jvm性能優化

Java程序員進階三條必經之路:數據庫、虛擬機、異步通訊。java

前言

入門JVM垃圾回收機制後,接下來能夠學習性能調優了。主要有兩部份內容:程序員

  1. JDK工具的使用。算法

  2. 調優策略。數據庫

兵器譜

jps

列出正在運行的虛擬機進程,用法以下:異步

jps [-option] [hostid]

| 選項 | 做用 |
| -------- | -----: |
| q | 只輸出LVMID,省略主類的名稱 |
| m | 輸出main method的參數 |
| l | 輸出徹底的包名,應用主類名,jar的徹底路徑名 |
| v | 輸出jvm參數 |jvm

jstat

監視虛擬機運行狀態信息,使用方式:ide

jstat -<option> <pid> [interval[s|ms]]

| 選項 | 做用 |
| -------- | -----: |
| gc | 輸出每一個堆區域的當前可用空間以及已用空間,GC執行的總次數,GC操做累計所花費的時間。|
| gccapactiy | 輸出每一個堆區域的最小空間限制(ms)/最大空間限制(mx),當前大小,每一個區域之上執行GC的次數。(不輸出當前已用空間以及GC執行時間)。|
| gccause | 輸出-gcutil提供的信息以及最後一次執行GC的發生緣由和當前所執行的GC的發生緣由。 |
| gcnew | 輸出新生代空間的GC性能數據。|
| gcnewcapacity | 輸出新生代空間的大小的統計數據。|
| gcold | 輸出老年代空間的GC性能數據。|
| gcoldcapacity | 輸出老年代空間的大小的統計數據。|
| gcpermcapacity | 輸出持久帶空間的大小的統計數據。|
| gcutil | 輸出每一個堆區域使用佔比,以及GC執行的總次數和GC操做所花費的事件。|
好比:工具

jstat -gc 28389 1s

每隔1秒輸出一次JVM運行信息:性能

S0C    S1C    S0U    S1U      EC       EU        OC         OU       PC     PU    YGC     YGCT    FGC    FGCT     GCT   
52416.0 52416.0 4744.9  0.0   419456.0 28180.6  2621440.0   439372.6  131072.0 33564.8 160472 1760.603  61      2.731 1763.334

| 列 | 說明 | jstat參數 |
| -------- | -----: | -----: |
| S0C | Survivor0空間的大小。單位KB。| -gc -gccapacity -gcnew -gcnewcapacity |
| S1C | Survivor1空間的大小。單位KB。| -gc -gccapacity -gcnew -gcnewcapacity |
| S0U | Survivor0已用空間的大小。單位KB。| -gc -gcnew |
| S1U | Survivor1已用空間的大小。單位KB。| -gc -gcnew |
| EC | Eden空間的大小。單位KB。| -gc -gccapacity -gcnew -gcnewcapacity |
| EU | Eden已用空間的大小。單位KB。| -gc-gcnew |
| OC | 老年代空間的大小。單位KB。| -gc -gccapacity -gcold -gcoldcapacity |
| OU | 老年代已用空間的大小。單位KB。| -gc -gcold |
| PC | 持久代空間的大小。單位KB。| -gc -gccapacity -gcold -gcoldcapacity -gcpermcapacity |
| PU | 持久代已用空間的大小。單位KB。| -gc -gcold |
| YGC | 新生代空間GC時間發生的次數。| -gc -gccapacity -gcnew -gcnewcapacity -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause |
| YGCT | 新生代GC處理花費的時間。| -gc-gcnew-gcutil-gccause |
| FGC | full GC發生的次數。| -gc -gccapacity -gcnew -gcnewcapacity -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause |
| FGCT | full GC操做花費的時間。| -gc -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause |
| GCT | GC操做花費的總時間。| -gc -gcold -gcoldcapacity -gcpermcapacity -gcutil -gccause |
| NGCMN | 新生代最小空間容量,單位KB。| -gccapacity -gcnewcapacity |
| NGCMX | 新生代最大空間容量,單位KB。| -gccapacity -gcnewcapacity |
| NGC | 新生代當前空間容量,單位KB。| -gccapacity -gcnewcapacity |
| OGCMN | 老年代最小空間容量,單位KB。 | -gccapacity-gcoldcapacity |
| OGCMX | 老年代最大空間容量,單位KB。| -gccapacity-gcoldcapacity |
| OGC | 老年代當前空間容量制,單位KB。| -gccapacity -gcoldcapacity |
| PGCMN | 持久代最小空間容量,單位KB。| -gccapacity -gcpermcapacity |
| PGCMX | 持久代最大空間容量,單位KB。| -gccapacity -gcpermcapacity |
| PGC | 持久代當前空間容量,單位KB。| -gccapacity -gcpermcapacity |
| PC | 持久代當前空間大小,單位KB。| -gccapacity-gcpermcapacity |
| PU | 持久代當前已用空間大小,單位KB。| -gc -gcold |
| LGCC | 最後一次GC發生的緣由。| -gccause |
| GCC | 當前GC發生的緣由。| -gccause |
| TT | 老年化閾值。被移動到老年代以前,在新生代空存活的次數。| -gcnew |
| MTT | 最大老年化閾值。被移動到老年代以前,在新生代空存活的次數。| -gcnew |
| DSS | 倖存者區所需空間大小,單位KB。| -gcnew |學習

jmap

生成堆存儲快照,使用方式:

jmap [ -option ] <pid>

| 選項 | 做用 |
| -------- | -----: |
| dump | 生成堆存儲快照,格式爲:-dump:[live, ]format=b, file=<filename>,live說明是否只dump出存活的對象。 |
| heap | 顯示java堆詳細信息,如使用那種回收器、參數配置、分代情況等。|
| histo | 顯示堆中對象統計信息,包括類、實例數量、合計容量。|

jstack

生成虛擬機當前時刻的線程快照,幫助定位線程出現長時間停頓的緣由,用法:

jstack <pid>
  1. Monitor

Monitor是 Java中用以實現線程之間的互斥與協做的主要手段,它能夠當作是對象或者Class的鎖。每個對象都有,也僅有一個 monitor。下面這個圖,描述了線程和 Monitor之間關係,以及線程的狀態轉換圖:

Paste_Image.png

進入區(Entrt Set):表示線程經過synchronized要求獲取對象的鎖,但並未獲得。
擁有者(The Owner):表示線程成功競爭到對象鎖。
等待區(Wait Set):表示線程經過對象的wait方法,釋放對象的鎖,並在等待區等待被喚醒。

  1. 線程狀態

    1. NEW,未啓動的。不會出如今Dump中。

    2. RUNNABLE,在虛擬機內執行的。

    3. BLOCKED,等待得到監視器鎖。

    4. WATING,無限期等待另外一個線程執行特定操做。

    5. TIMED_WATING,有時限的等待另外一個線程的特定操做。

    6. TERMINATED,已退出的。

舉個例子:

package com.jiuyan.mountain.test;

import java.util.concurrent.TimeUnit;

/**
 * Hello world!
 * 
 */
public class App {

     public static void main(String[] args) throws InterruptedException {
         MyTask task = new MyTask();
         Thread t1 = new Thread(task);
         t1.setName("t1");
         Thread t2 = new Thread(task);
             t2.setName("t2");
             t1.start();
             t2.start();
    }

}

class MyTask implements Runnable {

     private Integer mutex;
      
     public MyTask() {
         mutex = 1;
     }
      
     @Override
     public void run() {
         synchronized (mutex) {
             while(true) {
                 System.out.println(Thread.currentThread().getName());
                 try {
                     TimeUnit.SECONDS.sleep(5);
                 } catch (InterruptedException e) {
                     // TODO Auto-generated catch block
                     e.printStackTrace();
                 }
              }
          }
     }
    
}

線程狀態:

"t2" prio=10 tid=0x00007f7b2013a800 nid=0x67fb waiting for monitor entry [0x00007f7b17087000]
 java.lang.Thread.State: BLOCKED (on object monitor)
  at com.jiuyan.mountain.test.MyTask.run(App.java:35)
  - waiting to lock <0x00000007d6b6ddb8> (a java.lang.Integer)
  at java.lang.Thread.run(Thread.java:745)

"t1" prio=10 tid=0x00007f7b20139000 nid=0x67fa waiting on condition [0x00007f7b17188000]
 java.lang.Thread.State: TIMED_WAITING (sleeping)
  at java.lang.Thread.sleep(Native Method)

t1沒有搶到鎖,因此顯示BLOCKED。t2搶到了鎖,可是處於睡眠中,因此顯示TIMED_WAITING,有限等待某個條件來喚醒。
把睡眠的代碼去掉,線程狀態變成了:

"t2" prio=10 tid=0x00007fa0a8102800 nid=0x6a15 waiting for monitor entry [0x00007fa09e37a000]
 java.lang.Thread.State: BLOCKED (on object monitor)
  at com.jiuyan.mountain.test.MyTask.run(App.java:35)
  - waiting to lock <0x0000000784206650> (a java.lang.Integer)
  at java.lang.Thread.run(Thread.java:745)

 "t1" prio=10 tid=0x00007fa0a8101000 nid=0x6a14 runnable [0x00007fa09e47b000]
 java.lang.Thread.State: RUNNABLE
  at java.io.FileOutputStream.writeBytes(Native Method)

t1顯示RUNNABLE,說明正在運行,這裏須要額外說明一下,若是這個線程正在查詢數據庫,可是數據庫發生死鎖,雖然線程顯示在運行,實際上並無工做,對於IO型的線程別隻用線程狀態來判斷工做是否正常。
把MyTask的代碼小改一下,線程拿到鎖以後執行wait,釋放鎖,進入等待區。

public void run() {
     synchronized (mutex) {
         if(mutex == 1) {
             try {
                 mutex.wait();
             } catch (InterruptedException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
             }
         }
      }
  }

線程狀態以下:

"t2" prio=10 tid=0x00007fc5a8112800 nid=0x5a58 in Object.wait() [0x00007fc59b58c000]
 java.lang.Thread.State: WAITING (on object monitor)
  at java.lang.Object.wait(Native Method)

"t1" prio=10 tid=0x00007fc5a8111000 nid=0x5a57 in Object.wait() [0x00007fc59b68d000]
 java.lang.Thread.State: WAITING (on object monitor)
  at java.lang.Object.wait(Native Method)

兩個線程都顯示WAITING,此次是無限期的,須要從新得到鎖,因此後面跟了on object monitor
再來個死鎖的例子:

package com.jiuyan.mountain.test;

import java.util.concurrent.TimeUnit;

/**
 * Hello world!
 * 
 */
public class App {

  public static void main(String[] args) throws InterruptedException {
      MyTask task1 = new MyTask(true);
      MyTask task2 = new MyTask(false);
      Thread t1 = new Thread(task1);
      t1.setName("t1");
      Thread t2 = new Thread(task2);
      t2.setName("t2");
      t1.start();
      t2.start();
  }

}

class MyTask implements Runnable {

  private boolean flag;
  
  public MyTask(boolean flag) {
      this.flag = flag;
  }
  
  @Override
  public void run() {
      if(flag) {
          synchronized (Mutex.mutex1) {
              try {
                  TimeUnit.SECONDS.sleep(1);
              } catch (InterruptedException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
              synchronized (Mutex.mutex2) {
                  System.out.println("ok");
              }
          }
      } else {
          synchronized (Mutex.mutex2) {
              try {
                  TimeUnit.SECONDS.sleep(1);
              } catch (InterruptedException e) {
                  // TODO Auto-generated catch block
                  e.printStackTrace();
              }
              synchronized (Mutex.mutex1) {
                  System.out.println("ok");
              }
          }
      }
  }
  
}

class Mutex {
  public static Integer mutex1 = 1;
  public static Integer mutex2 = 2;
}

線程狀態:

"t2" prio=10 tid=0x00007f5f9c122800 nid=0x3874 waiting for monitor entry [0x00007f5f67efd000]
 java.lang.Thread.State: BLOCKED (on object monitor)
  at com.jiuyan.mountain.test.MyTask.run(App.java:55)
  - waiting to lock <0x00000007d6c45bd8> (a java.lang.Integer)
  - locked <0x00000007d6c45be8> (a java.lang.Integer)
  at java.lang.Thread.run(Thread.java:745)

"t1" prio=10 tid=0x00007f5f9c121000 nid=0x3873 waiting for monitor entry [0x00007f5f67ffe000]
 java.lang.Thread.State: BLOCKED (on object monitor)
  at com.jiuyan.mountain.test.MyTask.run(App.java:43)
  - waiting to lock <0x00000007d6c45be8> (a java.lang.Integer)
  - locked <0x00000007d6c45bd8> (a java.lang.Integer)
  at java.lang.Thread.run(Thread.java:745)

Found one Java-level deadlock:
=============================
"t2":
waiting to lock monitor 0x00007f5f780062c8 (object 0x00000007d6c45bd8, a java.lang.Integer),
which is held by "t1"
"t1":
waiting to lock monitor 0x00007f5f78004ed8 (object 0x00000007d6c45be8, a java.lang.Integer),
which is held by "t2"

這個有點像哲學家就餐問題,每一個線程都持有對方須要的鎖,那就運行不下去了。

調優策略

兩個基本原則:

  1. 將轉移到老年代的對象數量降到最少。

  2. 減小Full GC的執行時間。目標是Minor GC時間在100ms之內,Full GC時間在1s之內。

主要調優參數:

  1. 設定堆內存大小,這是最基本的。

    1. -Xms:啓動JVM時的堆內存空間。

    2. -Xmx:堆內存最大限制。

  2. 設定新生代大小。
    新生代不宜過小,不然會有大量對象涌入老年代。

    1. -XX:NewRatio:新生代和老年代的佔比。

    2. -XX:NewSize:新生代空間。

    3. -XX:SurvivorRatio:伊甸園空間和倖存者空間的佔比。

    4. -XX:MaxTenuringThreshold:對象進入老年代的年齡閾值。

  3. 設定垃圾回收器
    年輕代:-XX:+UseParNewGC。

老年代:-XX:+UseConcMarkSweepGC。 CMS能夠將STW時間降到最低,可是不對內存進行壓縮,有可能出現「並行模式失敗」。好比老年代空間還有300MB空間,可是一些10MB的對象沒法被順序的存儲。這時候會觸發壓縮處理,可是CMS GC模式下的壓縮處理時間要比Parallel GC長不少。 G1採用」標記-整理「算法,解決了內存碎片問題,創建了可預測的停頓時間類型,能讓使用者指定在一個長度爲M毫秒的時間段內,消耗在垃圾收集上的時間不得超過N毫秒。

相關文章
相關標籤/搜索