jstat -gcutil 输出结果分析_JVM故障分析
在上一篇文章《JVM實戰優化篇》中,梳理了JVM內存的核心參數,同時對新業務系統上線如何預估容量、垃圾回收器如何選擇進行了總結,最后文末還給大家總結了一套通用的JVM參數模板。JVM的調優基本都發生在上線前,盡量爭取在壓測環節就可以把JVM參數調整到最優,最有效的優化手段是架構和代碼層面的優化,如果上線以后發生了JVM故障,最常見的比如頻繁FullGC導致CPU飆升、系統響應遲鈍,OOM導致的服務不可用等場景,下面就工具和思路進行總結。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?大綱
一、JVM命令
1.jps,虛擬機進程狀況
./jps直接輸出各個JVM進程的PID。
2.jstat,虛擬機統計信息監視工具
jstat命令可以查看堆內存各部分的使用量,以及加載類的數量。主要常用這個查看堆的使用情況:
jstat -gc pidpid是VM的進程號,即當前運行的java進程號,後面還可以加幾個參數。
interval:執行的間隔時間,單位為毫秒
count :打印次數,如果缺省則一直打印
? ? ? ? ? ? ? ? ? ? 使用jstat -gc vmid
上面的縮寫解釋:E-eden, S-suviror, M-metaspace, C-capacity, U-used?前面均為瞬時統計容量(byte)
以下統計了從應用程序啟動到采樣時的統計綜合YGC?:年輕代中gc次數YGCT?:年輕代中gc所用時間(s)FGC:old代(全gc)gc次數FGCT:(全gc)gc所用時間(s)GCT:gc用的總時間(s)
3.jmap 虛擬機內存映像工具
jmap命令是一個可以輸出所有內存中對象的工具,甚至可以將VM 中的heap,以二進制輸出成文本。
堆轉儲文件:jmap -dump:live,format=b,file=heap-dump.bin
輸出所有內存中對象統計,jmap -gc pid,輸入的demo如下:
Attaching to process ID 5932, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.91-b15
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 1073741824 (1024.0MB)
NewSize = 42991616 (41.0MB)
MaxNewSize = 357564416 (341.0MB)
OldSize = 87031808 (83.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 60293120 (57.5MB)
used = 44166744 (42.120689392089844MB)
free = 16126376 (15.379310607910156MB)
73.25337285580842% used
From Space:
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
To Space:
capacity = 14680064 (14.0MB)
used = 0 (0.0MB)
free = 14680064 (14.0MB)
0.0% used
PS Old Generation
capacity = 120061952 (114.5MB)
used = 19805592 (18.888084411621094MB)
free = 100256360 (95.6119155883789MB)
16.496143590935453% used
20342 interned Strings occupying 1863208 bytes.
4.jstack 虛擬機堆棧跟蹤工具
jstack是JDK自帶的線程跟蹤工具,用於打印指定Java進程的線程堆棧信息。常用於排查cpu使用率過高的問題,其排查思路也比較套路:
1、通過top命令找出cpu使用率最高的進程
2、然后通過top -Hp pid查看該進程下各個線程的cpu使用情況
3、繼續使用jstack pid命令查看當前java進程的堆棧狀態,且將內容輸出到dump文件中。
4、將PID(十進制)轉換成十六進制后,用轉換后的數字去定位線程的一個執行狀態和堆棧信息。
5、也可以使用jca工具分析線程dump文件,找出來線程處于BLOCKED和WAITING狀態的,并進行堆棧分析
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?dump分析
可以重點懷疑那種應該很快執行但是並未執行的線程,比如dubbo調用等。
RUNNABLE,線程處于執行中
BLOCKED,線程被阻塞
WAITING,線程正在等待
二、頻繁FullGC實戰
FullGC發生時一般會導致CPU升高、系統響應變長,服務會短暫停頓等現象,如果頻繁的發生FGC的話,那麼就會導致系統的服務處於一個不穩定的狀況,引發FullGC的原因會很多,我們盡量要做的就是在上線之前,做好壓測,避免JVM參數的不合理導致頻繁FGC。
1.模擬壓測
模擬一個壓測場景,頻繁的創建對象,然後又很快的回收處理,分析步驟如下:
每秒執行一次loadData()方法
創建了40M的字節數組指向data變量,但是馬上成為垃圾;
data1和data2指向兩個10M數組,有引用而可以存活
data3依次指向了兩個10M數組
public static void main(String[] args) throws Exception{
Thread.sleep(1000);
while (true) {
loadData();
}
}
public static void loadData() throws Exception{
byte[] data = null;
for (int i = 0; i < 4; i++) {
data = new byte[10 * 1024 * 1024];
}
data = null;
byte[] data1 = new byte[10 * 1024 * 1024];
byte[] data2 = new byte[10 * 1024 * 1024];
byte[] data3 = new byte[10 * 1024 * 1024];
data3 = new byte[10 * 1024 * 1024];
Thread.sleep(1000);
}
}
此處設定的一個不太合理的JVM參數:
-Xms200M -Xmx200M -XX:NewSize=100m-XX:MaxNewSize=100m -XX:SurvivorRatio=8
-XX:PretenureSizeThreshold=20m -XX:+UseConcMarkSweepGC
-Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
100M新生代,Eden區80M,S區10M,Old區100M,1s內執行完loadData()方法產生80M垃圾,一定會觸發一次MinorGC。所以我們用jstat命令監控會發現:
第一次ygc,30M數組(data1、2、3)直接進入老年代(OU),data和data3的數組對象直接被回收。
后續每秒一次的ygc,都會有大約10~30M進入老年代(OU)
大約在60M的時候,發生了一次老年代GC,老年代的內存使用從60M->30M。
ygc太頻繁,導致每次回收后存活對象太多,頻繁進入老年代,頻繁觸發FullGC。
? ? ? ? ? ? ? ? ? ? ? ? ? 使用jstat -gc vmid
上面採用的jstat可以準確的去觀察數值上的精確變化,但是如果想利用可視化的工具,觀察一個總的盤面情況,可以採用VisualGC工具:
? ? ? ? ? ? ? ? ? ? ? ? visualGC觀察GC情況
2.優化思路
優化思路:擴大新生代,擴大Survivor區空間,堆大小300M,其中新生代擴大至200M(Eden區100M,S區各50M)。JVM參數設定如下:
-Xms300M -Xmx300M -XX:NewSize=200m -XX:MaxNewSize=200m-XX:SurvivorRatio=2 -XX:PretenureSizeThreshold=20m
-XX:+UseConcMarkSweepGC -Xloggc:gc.log
-XX:+PrintGCDetails -XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
此時再次觀察GC頻率和耗時每秒新增80M,每次ygc後有10M對象進入survivor區,8次ygc後才有600k數據進入老年代,FullGC已經完全消失。
? ? ? ? ? ? ? ? ? ? ??jstatc觀察gc情況
? ? ? ? ? ? ? ? ? ? ?? visualGC統計情況
3.FullGC原因及思路總結
上線前要充分做好壓測和JVM調優,這樣在線上就不會因為參數不合理而導致正常的業務運行而觸發了FullGC的情況。這裡調優的思路仍然遵循前面文章中的方案來進行:
讓短命對象在MinorGC階段就被回收;
MinorGC后的存活對象
長命對象盡早進入老年代,不要在新生代來回復制;
參數已經優化完畢,且上線一段時間以後,如果還會出現FullGC頻繁或者時間過長,分析思路就要從JVM調優轉化到架構和代碼調優上去,且需要一些特殊手段去排查GC問題,尤其需要結合dump分析來解決,首先要抓住FullGC時前後的堆dump日誌,然後再利用MAT工具進行堆棧分析和對象分布分析,這個工具的使用後面會繼續講到。這種層面的故障分析就需要結合業務+代碼一起聯合分析,而不是再去調整JVM參數能夠!
? ? ? ? ? ? ? ? ? ? ? ? ? ??FullGC原因及思路
如果FullGC無法準確的去抓取快照dump,那么還是可以在JVM啟動的時候增加下面兩個參數-XX:+HeapDumpBeforeFullGC -XX:+HeapDumpAfterFullGC就可以在FGC的前后dump一下內存。不過增加這兩個參數會在每次FGC的時候dump內存,dump內存本身對應用有影響,但是不會導致JVM停機。特別是在web應用啟動的時候,就會不斷的去dump內存,因為web應用啟動的時候會頻繁發生FGC,所以這個命令需要慎用。
三、OOM實戰
1.OOM類型及原因
之前的FullGC尚只是對系統的性能和使用造成一定的衝擊,影響至少可控,但是假如發生了OOM,那就相當於系統掛掉一樣,內存溢出無法再分配對象空間,那也就意味著系統無法接受任何請求,這個直接就是生產事故級別了。通常要分析OOM事件,就少不了要去排查內存洩漏的問題。內存洩漏是因,內存溢出是果:
內存泄露:申請使用完的內存沒有釋放,導致虛擬機不能再次使用該內存,此時這段內存就泄露了,因為申請者不用了,而又不能被虛擬機分配給別人用。
內存溢出:申請的內存超出了JVM能提供的內存大小,此時稱之為溢出。
能發生OOM的地方通常是四個:堆內,堆外,元空間(jdk8以前叫老年代)和虛擬機棧。
2.heap dump分析
為了能夠在OOM發生的時候能留下現場證據,線上服務的JVM參數中一般需要配置-XX:+HeapDumpOnOutOfMemoryError,在發生OOM之前將堆中對象和內存dump一份快照出來。我們這里模擬一個觸發OOM的場景,非常簡單,即無線的創造對象且不被回收,直到JVM內存打爆觸發OOM:
public class HeapOOM {static class OOMObject {
}
public static void main(String[] args) {
int i = 0;
Listlist = new ArrayList();
cycle(list, i);
}public static void cycle(Listlist, int i) {//在堆中無限創建對象while (true) {
System.out.println("already create objects=" + i++);list.add(new OOMObject());
}
}
}
同時設置一個JVM參數如下:
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/dump/執行主程序以后,沒過幾秒函數就掛掉了,此時我們拿出堆MAT dump工具進行分析:
1、查看對象分布餅圖,一般選擇前面幾個大對象進行分析
2、GC Roots線程追蹤 和 被引用分析,從創建最多的大對象里,右鍵點選進行GC Roots線程追蹤、incoming reference分析,可以把大對象的根引用對象以及所在的線程方法名打印出來,基本可以判斷大對象的出處和來源。
? ? ? ? ? ? ? ? ? ? ?gcroot追蹤和引用分析
3、通過MAT給出內存泄漏分析報告,查看診斷出來可能存在內存泄漏的線程棧和詳細的大對象信息。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?內存分析報告
下面就總結一下常見的OOM類型和原因,業務系統中最常發生就是堆內存的溢出,它的排查方法就是要結合dump日誌+mat分析工具,分析大對象類型,線程堆棧,並結合業務場景來分析為何會發生內存洩漏的原因。
? ? ? ? ? ? ? ? ? ? ? ? ? ?? OOM類型與原因
以上類型的OOM都已更新在我的github地址,每種類型除了給出了執行代碼,還需要同步修改IDE的jvm參數方能快速觸發OOM場景,下載地址在文章最末尾。
故障分析總結
綜合來看,故障分析篇,主要是三個核心觀點:
1、要在上線前的壓測環節將JVM參數調整至最優,後續就不在改動這一塊的參數。
2、如果上線以後還會發生了FullGC和OOM等情況,那麼有限需要去做架構和代碼層級的優化,這個時候分析GC日誌要比直接調優JVM參數要好。
3、JVM診斷和分析工具,利用好jstat或者VisualGC觀察GC次數和頻率,利用好jmap dump關鍵證據,然後使用MAT工具去!
推薦一個學習庫:
https://github.com/StabilityMan/StabilityGuide
實例中涉及到的代碼:
https://github.com/tisonkong/JavaAssemble/tree/master/jvm
總結
以上是生活随笔為你收集整理的jstat -gcutil 输出结果分析_JVM故障分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 三元组顺序表表示的稀疏矩阵加法_Matl
- 下一篇: python3多线程爬虫_【Python