Java程序員進階三條必經之路:數據庫、虛擬機、異步通訊。java
入門JVM垃圾回收機制後,接下來能夠學習性能調優了。主要有兩部份內容:程序員
JDK工具的使用。算法
調優策略。數據庫
列出正在運行的虛擬機進程,用法以下:異步
jps [-option] [hostid]
| 選項 | 做用 |
| -------- | -----: |
| q | 只輸出LVMID,省略主類的名稱 |
| m | 輸出main method的參數 |
| l | 輸出徹底的包名,應用主類名,jar的徹底路徑名 |
| v | 輸出jvm參數 |jvm
監視虛擬機運行狀態信息,使用方式: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 [ -option ] <pid>
| 選項 | 做用 |
| -------- | -----: |
| dump | 生成堆存儲快照,格式爲:-dump:[live, ]format=b, file=<filename>,live說明是否只dump出存活的對象。 |
| heap | 顯示java堆詳細信息,如使用那種回收器、參數配置、分代情況等。|
| histo | 顯示堆中對象統計信息,包括類、實例數量、合計容量。|
生成虛擬機當前時刻的線程快照,幫助定位線程出現長時間停頓的緣由,用法:
jstack <pid>
Monitor
Monitor是 Java中用以實現線程之間的互斥與協做的主要手段,它能夠當作是對象或者Class的鎖。每個對象都有,也僅有一個 monitor。下面這個圖,描述了線程和 Monitor之間關係,以及線程的狀態轉換圖:
進入區(Entrt Set):表示線程經過synchronized要求獲取對象的鎖,但並未獲得。
擁有者(The Owner):表示線程成功競爭到對象鎖。
等待區(Wait Set):表示線程經過對象的wait方法,釋放對象的鎖,並在等待區等待被喚醒。
線程狀態
NEW,未啓動的。不會出如今Dump中。
RUNNABLE,在虛擬機內執行的。
BLOCKED,等待得到監視器鎖。
WATING,無限期等待另外一個線程執行特定操做。
TIMED_WATING,有時限的等待另外一個線程的特定操做。
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"
這個有點像哲學家就餐問題,每一個線程都持有對方須要的鎖,那就運行不下去了。
兩個基本原則:
將轉移到老年代的對象數量降到最少。
減小Full GC的執行時間。目標是Minor GC時間在100ms之內,Full GC時間在1s之內。
主要調優參數:
設定堆內存大小,這是最基本的。
-Xms:啓動JVM時的堆內存空間。
-Xmx:堆內存最大限制。
設定新生代大小。
新生代不宜過小,不然會有大量對象涌入老年代。
-XX:NewRatio:新生代和老年代的佔比。
-XX:NewSize:新生代空間。
-XX:SurvivorRatio:伊甸園空間和倖存者空間的佔比。
-XX:MaxTenuringThreshold:對象進入老年代的年齡閾值。
設定垃圾回收器
年輕代:-XX:+UseParNewGC。
老年代:-XX:+UseConcMarkSweepGC。 CMS能夠將STW時間降到最低,可是不對內存進行壓縮,有可能出現「並行模式失敗」。好比老年代空間還有300MB空間,可是一些10MB的對象沒法被順序的存儲。這時候會觸發壓縮處理,可是CMS GC模式下的壓縮處理時間要比Parallel GC長不少。 G1採用」標記-整理「算法,解決了內存碎片問題,創建了可預測的停頓時間類型,能讓使用者指定在一個長度爲M毫秒的時間段內,消耗在垃圾收集上的時間不得超過N毫秒。