JVM效能優化入門指南
前言
入門JVM垃圾回收機制後,接下來可以學習效能調優了。主要有兩部分內容:
- JDK工具的使用。
- 調優策略。
兵器譜
jps
列出正在執行的虛擬機器程式,用法如下:
jps [-option] [hostid]
選項 | 作用 |
---|---|
q | 只輸出LVMID,省略主類的名稱 |
m | 輸出main method的引數 |
l | 輸出完全的包名,應用主類名,jar的完全路徑名 |
v | 輸出jvm引數 |
jstat
監視虛擬機器執行狀態資訊,使用方式:
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>
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毫秒。
相關文章
- Hbase優化入門優化
- ASP.NET 效能監控和優化入門ASP.NET優化
- ASP.NET效能監控和優化入門ASP.NET優化
- Android外掛化入門指南Android
- JVM效能優化(一)JVM技術入門JVM優化
- 前端必看的資料視覺化入門指南前端視覺化
- JVM效能優化JVM優化
- 【JAVA進階架構師指南】之五:JVM效能調優Java架構JVM
- JVM效能優化 (一) 初識JVMJVM優化
- 斜率最佳化入門
- JVM(一)史上最佳入門指南JVM
- 辦公自動化入門指南,從此告別繁瑣操作
- 前端效能優化指南前端優化
- RedHat 效能調優指南Redhat
- MySQL效能優化指南MySql優化
- MongoDB效能優化指南MongoDB優化
- 深入理解JVM效能調優JVM
- 《java學習二》jvm效能優化-----認識jvmJavaJVM優化
- 《java學習三》jvm效能優化-------調優JavaJVM優化
- JVM效能調優與實戰篇JVM
- 前端時間國際化入門前端
- 《Java效能優化權威指南》的邊邊角(2)——理解JVM-系統鎖Java優化JVM
- JVM效能調優,記憶體分析工具JVM記憶體
- Linux CPU 效能優化指南Linux優化
- Vue 應用效能優化指南Vue優化
- PHP 應用效能優化指南PHP優化
- MySQL分頁效能優化指南MySql優化
- Angular應用效能優化指南Angular優化
- 漫談Nuclear Web元件化入門篇Web元件化
- Python 效能分析入門指南Python
- 讀書筆記:《SEO教程:搜尋引擎優化入門與進階》(4)——程式碼優化筆記優化
- 《java學習三》jvm效能優化------jconsulJavaJVM優化
- 效能調優(cpu/IO/JVM記憶體分析)JVM記憶體
- JVM效能優化,提高Java的伸縮性JVM優化Java
- 系列化入門讀物-PART 3 (轉)
- spark效能調優指南高階篇Spark
- React 16 載入效能優化指南React優化
- Spark效能優化指南:高階篇Spark優化