來源:博客園 作者:bojiangzhou
鏈接:https://www.cnblogs.com/chiangchou/p/jvm-4.html
一娇豫、JDK工具
先來看看有哪些常用的工具可以輔助我們進(jìn)行性能調(diào)優(yōu)和問題排查,后面再通過一個(gè)具體的示例結(jié)合工具來分析調(diào)優(yōu)。
1湿诊、JDK工具
JDK自帶了很多性能監(jiān)控工具,我們可以用這些工具來監(jiān)測系統(tǒng)和排查內(nèi)存性能問題。
2境氢、利用 jps 找出進(jìn)程
jps(Java Virtual Machine Process Status Tool)是JDK 1.5提供的一個(gè)顯示當(dāng)前所有java進(jìn)程pid的命令,簡單實(shí)用碰纬,非常適合在linux/unix平臺上簡單察看當(dāng)前java進(jìn)程的一些簡單情況萍聊。
1)查看Java進(jìn)程PID
【jps -l】左邊一列就是Java進(jìn)程的PID。
2)輸出傳遞給JVM的參數(shù)
【jps -vl】
3悦析、利用 jstat 查看VM統(tǒng)計(jì)信息
使用 jstat 工具可以監(jiān)測 Java 應(yīng)用程序的實(shí)時(shí)運(yùn)行情況寿桨,可以看到VM內(nèi)的Eden、Survivor强戴、老年代的內(nèi)存使用情況亭螟,還有 YoungGC 和 FullGC 的執(zhí)行次數(shù)以及耗時(shí)。通過這些指標(biāo)骑歹,我們可以輕松的分析出當(dāng)前系統(tǒng)的運(yùn)行情況预烙,判斷當(dāng)前系統(tǒng)的內(nèi)存使用壓力以及GC壓力,還有內(nèi)存分配是否合理道媚。
1)查看 jstat 有哪些操作
【jstat -options】
- -class:顯示 ClassLoad 的相關(guān)信息扁掸;
- -compiler:顯示 JIT 編譯的相關(guān)信息;
- -gc:顯示和 gc 相關(guān)的堆信息最域;
- -gccapacity:顯示各個(gè)代的容量以及使用情況谴分;
- -gcmetacapacity:顯示 Metaspace 的大小镀脂;
- -gcnew:顯示新生代信息牺蹄;
- -gcnewcapacity:顯示新生代大小和使用情況;
- -gcold:顯示老年代和永久代的信息狗热;
- -gcoldcapacity :顯示老年代的大谐佟;
- -gcutil:顯示垃圾收集信息匿刮;
- -gccause:顯示垃圾回收的相關(guān)信息(同 -gcutil),同時(shí)顯示最后一次或當(dāng)前正在發(fā)生的垃圾回收的誘因探颈;
- -printcompilation:輸出 JIT 編譯的方法信息
其中 jstat -gc 是最完整熟丸、最常用、最實(shí)用的命令伪节,基本足夠分析jvm的運(yùn)行情況了光羞。
2)顯示 ClassLoad 的相關(guān)信息
【jstat -class <pid>】
3)查看內(nèi)存使用和GC情況
【jstat -gc <pid> [<interval> [<count>]】
- S0C:年輕代中 To Survivor 的容量(單位 KB)绩鸣;
- S1C:年輕代中 From Survivor 的容量(單位 KB);
- S0U:年輕代中 To Survivor 目前已使用空間(單位 KB)纱兑;
- S1U:年輕代中 From Survivor 目前已使用空間(單位 KB)呀闻;
- EC:年輕代中 Eden 的容量(單位 KB);
- EU:年輕代中 Eden 目前已使用空間(單位 KB)潜慎;
- OC:老年代的容量(單位 KB)捡多;
- OU:老年代目前已使用空間(單位 KB);
- MC:Metaspace 的容量(單位 KB)铐炫;
- MU:Metaspace 目前已使用空間(單位 KB)垒手;
- CCSC:壓縮類空間大小
- CCSU:壓縮類空間使用大小
- YGC:從應(yīng)用程序啟動(dòng)到采樣時(shí)年輕代中 gc 次數(shù);
- YGCT:從應(yīng)用程序啟動(dòng)到采樣時(shí)年輕代中 gc 所用時(shí)間 (s)倒信;
- FGC:從應(yīng)用程序啟動(dòng)到采樣時(shí) old 代(全 gc)gc 次數(shù)科贬;
- FGCT:從應(yīng)用程序啟動(dòng)到采樣時(shí) old 代(全 gc)gc 所用時(shí)間 (s);
- GCT:從應(yīng)用程序啟動(dòng)到采樣時(shí) gc 用的總時(shí)間 (s)
4)查看垃圾回收統(tǒng)計(jì)
【jstat -gcutil <pid> [<interval> [<count>]】
- S0:Survivor0 區(qū)占用百分比
- S1:Survivor1 區(qū)占用百分比
- E:Eden 區(qū)占用百分比
- O:老年代占用百分比
- M:元數(shù)據(jù)區(qū)占用百分比
- YGC:年輕代回收次數(shù)
- YGCT:年輕代回收耗時(shí)
- FGC:老年代回收次數(shù)
- FGCT:老年代回收耗時(shí)
- GCT:GC總耗時(shí)
4鳖悠、利用 jmap 查看對象分布情況
使用 jmap 可查看堆內(nèi)存初始化配置信息以及堆內(nèi)存的使用情況榜掌,輸出堆內(nèi)存中的對象信息,包括產(chǎn)生了哪些對象乘综,對象數(shù)量多少等唐责。
1)查看堆內(nèi)存情況
【jmap -heap <PID>】
這個(gè)命令會打印出堆內(nèi)存相關(guān)的一些參數(shù)設(shè)置以及各個(gè)區(qū)域的情況,要查看這些信息一般使用 jstat 命令就足夠了瘾带。
2)查看系統(tǒng)運(yùn)行時(shí)對象分布
【jmap -histo[:live] <PID>】帶上 live 則只統(tǒng)計(jì)活對象
這個(gè)命令會按照各種對象占用內(nèi)存空間的大小降序排列鼠哥,把占用內(nèi)存最多的對象放在最上面。通過這個(gè)命令可以簡單的了解下當(dāng)前jvm中的對象對內(nèi)存占用的情況以及當(dāng)前內(nèi)存里到底是哪個(gè)對象占用了大量的內(nèi)存空間看政。
3)生成堆內(nèi)存轉(zhuǎn)儲快照
【jmap -dump:format=b,file=<path> <pid>】
【jmap -dump:live,format=b,file=<path> <pid>】
jmap -dump 是輸出堆中所有對象朴恳;jmap -dump:live 是輸出堆中所有活著的對象,而且 jmap -dump:live 會觸發(fā) FullGC允蚣,線上使用要注意于颖。format=b 是以二進(jìn)制格式輸出;file 是文件路徑嚷兔,格式為 hrpof 后綴森渐。
這個(gè)命令會在當(dāng)前目錄下生成一個(gè) dump.hrpof 文件,這是個(gè)二進(jìn)制的格式冒晰,無法直接打開同衣,可以使用MAT等工具來分析。這個(gè)命令把這一時(shí)刻VM堆內(nèi)存里所有對象的快照放到文件里去了壶运,供你后續(xù)去分析耐齐。
5、利用 jstack 分析線程棧
jstack 是一種線程堆棧分析工具,最常用的功能就是使用 jstack pid 命令查看線程的堆棧信息埠况,通常會結(jié)合 top -Hp pid 或 pidstat -p pid -t 一起查看具體線程的狀態(tài)耸携,也經(jīng)常用來排查一些死鎖的異常、CPU占用高的線程等辕翰。
1)jstack參數(shù)
- -l:長列表. 打印關(guān)于鎖的附加信息夺衍,例如屬于 java.util.concurrent 的 ownable synchronizers 列表。
- -F:當(dāng) jstack [-l] pid 沒有響應(yīng)的時(shí)候強(qiáng)制打印棧信息
- -m:打印 java 和 native c/c++ 框架的所有棧信息.
- -h | -help:打印幫助信息
2)查看線程堆棧信息
【jstack <pid> > stack.log】
這個(gè)命令可以把程序的線程堆棧dump下來喜命。每個(gè)線程堆棧的信息中沟沙,都可以查看到線程 ID、線程狀態(tài)(wait渊抄、sleep尝胆、running 等狀態(tài))以及是否持有鎖等。
- pool-11-thread-6:線程名稱
-
1920:線程編號
- prio=5:線程的優(yōu)先級別
- os_prio=0:系統(tǒng)級別的線程優(yōu)先級
- tid=0x00007f87e028c000:線程ID
- nid=0x6724:native線程的id护桦,通過 printf "%x\n" <pid> 命令轉(zhuǎn)換線程ID
- waiting on condition [0x00007f87b97d2000]:線程當(dāng)前的狀態(tài)
二含衔、Linux 命令行工具
1、top 命令
top 命令是我們在 Linux 下最常用的命令之一二庵,它可以實(shí)時(shí)顯示正在執(zhí)行進(jìn)程的 CPU 使用率贪染、內(nèi)存使用率以及系統(tǒng)負(fù)載等信息。其中上半部分顯示的是系統(tǒng)的統(tǒng)計(jì)信息催享,下半部分顯示的是進(jìn)程的使用率統(tǒng)計(jì)信息杭隙。
看第一行:主要展示了CPU的負(fù)載情況
- 23:22:23:指的是當(dāng)前時(shí)間
- up 12 days, 12::18:指的是機(jī)器已經(jīng)運(yùn)行了多長時(shí)間
- 1 user:當(dāng)前機(jī)器有一個(gè)用戶在使用
- load average: 0.19, 0.27, 0.30:指 CPU 在1分鐘、5分鐘因妙、15分鐘內(nèi)的負(fù)載情況痰憎。
最重要的就是看 load average,比如機(jī)器是4核CPU攀涵,那么 0.19铣耘、0.27、0.30以故,說明4核中連一個(gè)核都沒用滿蜗细,4核CPU基本很空閑。如果CPU負(fù)載是1怒详,說明有1個(gè)核被使用的比較繁忙了炉媒。如果負(fù)載是4,說明4核CPU都跑滿了昆烁;如果超過4吊骤,說明4核CPU被繁忙的使用還不夠處理當(dāng)前的任務(wù),很多進(jìn)程可能一直在等待CPU去執(zhí)行自己的任務(wù)善玫。
② 查看具體線程使用系統(tǒng)資源情況
2水援、vmstat 命令
vmstat 是 Virtual Meomory Statistics(虛擬內(nèi)存統(tǒng)計(jì))的縮寫密强,可對操作系統(tǒng)的虛擬內(nèi)存茅郎、進(jìn)程蜗元、CPU活動(dòng)進(jìn)行監(jiān)控。
命令格式:【vmstat [ 選項(xiàng) ] [ <時(shí)間間隔> ] [ <次數(shù)> ]】
字段說明:
- Procs(進(jìn)程):
- r:等待運(yùn)行的進(jìn)程數(shù)
- b:處于非中斷睡眠狀態(tài)的進(jìn)程數(shù)
- Memory(內(nèi)存系冗,單位Kb):
- swpd:虛擬內(nèi)存使用情況
- free:空閑的內(nèi)存
- buff:用來作為緩沖的內(nèi)存數(shù)
- cache:用作緩存的內(nèi)存大小
- Swap(交換區(qū)):
- si:從磁盤交換到內(nèi)存的交換頁數(shù)量
- so:從內(nèi)存交換到磁盤的交換頁數(shù)量
- IO:(現(xiàn)在的Linux版本塊的大小為1024bytes)
- bi:發(fā)送到塊設(shè)備的塊數(shù)
- bo:從塊設(shè)備接收到的塊數(shù)
- System(系統(tǒng)):
- in:每秒中斷數(shù)奕扣,包括時(shí)鐘中斷≌凭矗【interrupt】
- cs:每秒上下文切換數(shù)惯豆。【count/second】
- CPU(以百分比表示):
- us:用戶 CPU 使用時(shí)間(user time)
- sy:內(nèi)核 CPU 系統(tǒng)使用時(shí)間 (system time)
- id:空閑時(shí)間(包括IO等待時(shí)間)奔害,中央處理器的空閑時(shí)間 楷兽。以百分比表示。
- wa:等待IO時(shí)間
判斷指標(biāo):
- 如果 r 經(jīng)常大于4华临,id 經(jīng)常少于40芯杀,表示cpu的負(fù)荷很重。
- 如果 bi雅潭,bo 長期不等于0揭厚,表示內(nèi)存不足。
- 如果 disk 經(jīng)常不等于0扶供,且在 b 中的隊(duì)列大于3筛圆,表示io性能不好。
- 通過 cs 觀察 Java 程序運(yùn)行過程中系統(tǒng)的上下文切換頻率椿浓。過高說明程序創(chuàng)建了過多的線程導(dǎo)致頻繁的上下文切換太援。
3、pidstat 命令
如果是監(jiān)視某個(gè)應(yīng)用的上下文切換扳碍,可以使用 pidstat 命令監(jiān)控指定進(jìn)程的上下文切換提岔。
pidstat 是 Sysstat 中的一個(gè)組件,也是一款功能強(qiáng)大的性能監(jiān)測工具左腔,我們可以通過命令:yum install sysstat 安裝該監(jiān)控組件唧垦。top 和 vmstat 兩個(gè)命令都是監(jiān)測進(jìn)程的內(nèi)存、CPU 以及 I/O 使用情況液样,而 pidstat 命令則是深入到線程級別振亮。
命令格式:【pidstat [ 選項(xiàng) ] [ <時(shí)間間隔> ] [ <次數(shù)> ]】
1)常用的選項(xiàng):
- -u:默認(rèn)的參數(shù),顯示各個(gè)進(jìn)程的 cpu 使用情況
- -r:顯示各個(gè)進(jìn)程的內(nèi)存使用情況
- -d:顯示各個(gè)進(jìn)程的 I/O 使用情況
- -p:指定進(jìn)程號
- -w:顯示每個(gè)進(jìn)程的上下文切換情況
- -t:顯示進(jìn)程中線程的統(tǒng)計(jì)信息
- -T { TASK | CHILD | ALL }
- TASK表示報(bào)告獨(dú)立的task鞭莽,CHILD關(guān)鍵字表示報(bào)告進(jìn)程下所有線程統(tǒng)計(jì)信息坊秸。ALL表示報(bào)告獨(dú)立的task和task下面的所有線程。
- 注意:task和子線程的全局的統(tǒng)計(jì)信息和pidstat選項(xiàng)無關(guān)。這些統(tǒng)計(jì)信息不會對應(yīng)到當(dāng)前的統(tǒng)計(jì)間隔离咐,這些統(tǒng)計(jì)信息只有在子線程kill或者完成的時(shí)候才會被收集。
- -V:版本號
- -h:在一行上顯示了所有活動(dòng)偏灿,這樣其他程序可以容易解析星瘾。
- -I:在SMP環(huán)境走孽,表示任務(wù)的CPU使用率/內(nèi)核數(shù)量
- -l:顯示命令名和所有參數(shù)
2)查看所有進(jìn)程的 CPU 使用情況
【pidstat】、【pidstat -u -p ALL】
- PID:進(jìn)程ID
- %usr:進(jìn)程在用戶空間占用cpu的百分比
- %system:進(jìn)程在內(nèi)核空間占用cpu的百分比
- %guest:進(jìn)程在虛擬機(jī)占用cpu的百分比
- %CPU:進(jìn)程占用cpu的百分比
- CPU:處理進(jìn)程的cpu編號
- Command:當(dāng)前進(jìn)程對應(yīng)的命令
3)顯示每個(gè)進(jìn)程的上下文切換情況
【pidstat -w -p <PID> <時(shí)間間隔> <次數(shù)>】
- PID:進(jìn)程id
- Cswch/s:每秒主動(dòng)任務(wù)上下文切換數(shù)量
- Nvcswch/s:每秒被動(dòng)任務(wù)上下文切換數(shù)量
- Command:命令名
4)顯示進(jìn)程中線程的統(tǒng)計(jì)信息
【pidstat -p <PID> -t】
三琳状、可視化工具
下面簡單介紹幾款常用的可視化分析工具磕瓷,一般我們需要將GC日志文件、堆轉(zhuǎn)儲文件dump下來念逞,然后就可以通過這些工具來分析困食。如果是線上分析一般直接使用上面的那些JDK命令行工具就足夠了,這些可視化工具可以做一些輔助性的分析翎承。
1硕盹、jvisualvm — JVM監(jiān)控
jvisualvm 是 jdk 提供的監(jiān)控工具,位于 %JAVA_HOME%/bin/jvisualvm.exe叨咖,雙擊運(yùn)行即可瘩例。
VisualVM 提供了一個(gè)可視界面,用于查看JVM上運(yùn)行的基于 Java 技術(shù)的應(yīng)用程序的詳細(xì)信息芒澜。VisualVM 能夠監(jiān)控線程仰剿,內(nèi)存情況,方法的CPU時(shí)間和內(nèi)存中的對象痴晦,已被GC的對象南吮,反向查看分配的堆棧(如100個(gè)String對象分別由哪幾個(gè)對象分配出來的)等。
更詳細(xì)的一些使用方式可以參考這篇文章:https://zhuanlan.zhihu.com/p/30837957
1)插件安裝
VisualVM 基于NetBeans平臺開發(fā)工具誊酌,它具備通過插件擴(kuò)展功能的能力部凑,有了插件擴(kuò)展支持,VisualVM可以做到:
- 顯示虛擬機(jī)進(jìn)程以及進(jìn)程的配置碧浊、環(huán)境信息(jps涂邀、jinfo)
- 監(jiān)視應(yīng)用程序的處理器、垃圾收集箱锐、堆比勉、方法區(qū)以及線程的信息(jstat、jstack)
- dump以及分析堆轉(zhuǎn)儲快照(jmap驹止、jhat)
- 方法級的程序運(yùn)行性能分析浩聋,找出被調(diào)用最多、運(yùn)行時(shí)間最長的方法
- 離線程序快照:收集程序的運(yùn)行時(shí)配置臊恋、線程dump衣洁、內(nèi)存dump等信息建立一個(gè)快照,可以將快照發(fā)送開發(fā)者處進(jìn)行Bug反饋
- 其他插件帶來的無限可能性
可以從工具選項(xiàng)中打開插件面板安裝所需的插件:
2)監(jiān)視面板
在左邊選擇需要監(jiān)控的程序抖仅,右邊就可以可查看CPU坊夫、堆砖第、線程等波動(dòng)情況,也可以直接在這里進(jìn)行手動(dòng) GC 和堆 Dump 操作环凿。
3)線程面板
可看到所有的線程梧兼,以及線程的運(yùn)行狀態(tài)。點(diǎn)擊面板的線程 Dump 按鈕拷邢,可以查看線程瞬時(shí)的線程棧袱院。(通過 jstack 也可以抓取線程棧)
4)GC面板
可以很方便的看到GC趨勢圖屎慢。(也可使用 jstat 工具監(jiān)控)
5)分析堆轉(zhuǎn)儲快照
通過左上角裝入快照按鈕打開 dump 的堆轉(zhuǎn)儲文件瞭稼,就可以分析堆轉(zhuǎn)儲快照了。
3腻惠、GCViewer — 離線分析GC日志
GCViewer 可以離線查看GC日志环肘,下載地址為 https://github.com/chewiebug/GCViewer。下載下來之后執(zhí)行 [mvn clean install -Dmaven.test.skip=true] 命令打包編譯集灌,編譯完成后在target目錄下會看到j(luò)ar包悔雹,然后在命令行運(yùn)行這個(gè)jar包就可以啟動(dòng) GCViewer。
然后通過 File 打開GC日志文件欣喧,就可以看到GC統(tǒng)計(jì)圖和GC情況腌零。通過工具,我們可以看到吞吐量唆阿、停頓時(shí)間以及 GC 的頻率等信息益涧,從而可以非常直觀地了解到 GC 的性能情況。
4驯鳖、GCeasy — 在線分析GC日志
GCeasy 是一款在線版的非常直觀的 GC 日志分析工具闲询,我們可以將日志文件壓縮之后,上傳到 GCeasy 官網(wǎng)即可看到非常清楚的 GC 日志分析結(jié)果浅辙。
5扭弧、FastThread — 分析線程棧
線程棧使用 jstack 命令 dump 下來后,可以使用 FastThread 在線工具分析線程棧信息记舆,可以直觀的看到有多少線程鸽捻、線程池、線程的狀態(tài)泽腮、是否有死鎖等信息御蒲。
6、MAT — 分析堆轉(zhuǎn)儲文件
我們使用 jmap 命令 dump 下來的堆轉(zhuǎn)儲文件可以使用 MAT 來分析盛正,可以分析創(chuàng)建了哪些對象删咱,然后分析對象的引用鏈,找出問題所在豪筝。MAT 下載地址:http://www.eclipse.org/mat/downloads.php
MAT 主要功能:
- 找出內(nèi)存泄漏的原因
- 找出重復(fù)引用的類和jar
- 分析集合的使用
- 分析類加載器
7痰滋、性能調(diào)優(yōu)工具
1)在線工具地址
線程 Dump 分析(FastThread):https://fastthread.io/
線程 Dump 分析(PerfMa):https://thread.console.perfma.com/
內(nèi)存 Dump 分析(PerfMa):https://memory.console.perfma.com/
JVM 參數(shù)分析(PerfMa):https://opts.console.perfma.com/
GC 日志分析(GCEasy):https://www.gceasy.io/
GC 日志分析(GCViewer):https://github.com/chewiebug/GCViewer
Java 診斷(Arthas):https://alibaba.github.io/arthas/
MAT:http://www.eclipse.org/mat/downloads.php
2)調(diào)優(yōu)工具選擇
調(diào)優(yōu)的工具很多摘能,一般對于線上系統(tǒng),使用 jstat 工具足以分析出JVM的運(yùn)行情況敲街,如果有GC日志团搞,也可以使用GCeasy快速分析JVM的運(yùn)行情況。
遇到CPU負(fù)載過高多艇,可以使用 top + pidstat 找出負(fù)載高的線程逻恐,或者直接使用 jstat 觀察是不是在頻繁FullGC。
遇到死鎖峻黍、OOM等問題复隆,可以用 jstack 把線程棧dump下來分析,還可以結(jié)合 FastThread 在線工具姆涩,分析線程棧挽拂、哪些線程阻塞等待等。
遇到OOM問題骨饿,使用 jmap 把堆轉(zhuǎn)儲快照dump下來亏栈,用MAT來分析創(chuàng)建了哪些大量的對象。
8宏赘、JVM監(jiān)控平臺
系統(tǒng)上線后绒北,如果不部署可視化的監(jiān)控平臺,我們一般就通過上面的這些工具來分析JVM的內(nèi)存運(yùn)轉(zhuǎn)模型察署、GC情況等闷游,可以在機(jī)器上運(yùn)行jstat,讓其把監(jiān)控信息寫入一個(gè)文件箕母,每天定時(shí)檢查—下储藐。
監(jiān)控平臺則可以通過可視化的界面看到JVM各個(gè)區(qū)域的內(nèi)存變化,GC次數(shù)和GC耗時(shí)等信息嘶是,以及出現(xiàn)性能問題時(shí)能及時(shí)報(bào)警钙勃。
一般可以部署 Zabbix、Ganglia聂喇、Open-Falcon辖源、Prometheus 之類的可視化監(jiān)控平臺,把線上系統(tǒng)接入到這些平臺希太,就可以直接圖形化看到JVM的表現(xiàn)克饶。
四、利用工具分析JVM運(yùn)行情況
要想合理地分配內(nèi)存誊辉、優(yōu)化GC矾湃,通過前一篇的性能調(diào)優(yōu)過程可以發(fā)現(xiàn),我們至少需要知道如下的一些信息:新生代對象增長的速率堕澄,YoungGC的觸發(fā)頻率邀跃,YoungGC的耗時(shí)霉咨,每次YoungGC后存活對象大小,每次YoungGC過后有多少對象進(jìn)入了老年代拍屑,老年代對象增長的速率途戒,F(xiàn)ullGC的觸發(fā)頻率,F(xiàn)ullGC的耗時(shí)等僵驰。前面我們是通過分析GC日志或者粗略估算的方式來調(diào)優(yōu)的喷斋,現(xiàn)在就利用 jstat 工具來分析下。
1蒜茴、運(yùn)行示例程序
1)如下示例代碼
這段代碼模擬每秒鐘在新生代創(chuàng)建20M對象星爪,1秒之后就變?yōu)槔鴮ο罅恕?/p>
public class GCMain {
static final int _1M = 1024 * 1024;
public static void main(String[] args) {
sleep(20);
for (int i = 0; i < 100; i++) {
loadData(i);
}
}
// loadData 每次請求產(chǎn)生20M對象,每次請求耗時(shí)1秒
public static void loadData(int index) {
System.out.println("load data: " + index);
byte[] data1 = new byte[_1M * 10];
byte[] data2 = new byte[_1M * 10];
sleep(1);
}
public static void sleep(long seconds) {
try {
Thread.sleep(seconds * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2)設(shè)置JVM參數(shù)
運(yùn)行示例程序前設(shè)置如下的JVM參數(shù):新生代矮男、老年代各100M移必,Eden區(qū)80M,Survivor區(qū)10M毡鉴,大對象閥值20M。
-Xms200M
-Xmx200M
-Xmn100M
-XX:SurvivorRatio=8
-XX:MaxTenuringThreshold=5
-XX:PretenureSizeThreshold=20M
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=92
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:./gc.log
3)估算內(nèi)存運(yùn)轉(zhuǎn)模型
我們先根據(jù)這段業(yè)務(wù)代碼以及JVM參數(shù)配置估算下JVM運(yùn)行情況:
- 這段代碼每秒將在Eden區(qū)產(chǎn)生20M對象秒赤,大概3~4秒鐘會占滿Eden區(qū)觸發(fā)YoungGC猪瞬。
- YoungGC 后存活的對象可能超過10M,因?yàn)榭赡茉趧?chuàng)建 data2 時(shí)入篮,Eden區(qū)不夠了陈瘦,而 data1 還是存活的;也有可能為0潮售,在創(chuàng)建 data1 的時(shí)候 Eden 區(qū)就不夠了痊项。
- 由于 Survivor 區(qū)不足以放下YoungGC后存活的對象,那么每次大概會有10M的對象進(jìn)入老年代酥诽;考慮到有可能YoungGC后沒有存活對象鞍泉,就估算為2次YoungGC會有10M進(jìn)入老年代吧。
- YoungGC 3~4 秒觸發(fā)一次肮帐,那么大概經(jīng)過18次左右YoungGC咖驮,就是60秒左右,老年代就快滿了训枢,然后存活對象無法放入老年代觸發(fā)FullGC托修。
- 由于CMS后臺回收線程在老年代超過92%時(shí)會觸發(fā)OldGC,所以60秒左右也有可能由于老年代超過92%這個(gè)閥值觸發(fā)GC恒界。
4)使用 jps 命令找出程序的 PID
將程序運(yùn)行起來睦刃,首先通過 jps -l 命令找到這個(gè)程序的PID。
5)使用 jstat 命令查看GC情況
如下是 jstat 輸出的情況:
Mechrevo@hello-world MINGW64 ~/Desktop
$ jstat -gc 15488 1000 1000
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
10240.0 10240.0 0.0 0.0 81920.0 4984.1 102400.0 0.0 4480.0 786.6 384.0 76.4 0 0.000 0 0.000 0.000
10240.0 10240.0 0.0 0.0 81920.0 4984.1 102400.0 0.0 4480.0 786.6 384.0 76.4 0 0.000 0 0.000 0.000
10240.0 10240.0 0.0 0.0 81920.0 4984.1 102400.0 0.0 4480.0 786.6 384.0 76.4 0 0.000 0 0.000 0.000
10240.0 10240.0 0.0 0.0 81920.0 4984.1 102400.0 0.0 4480.0 786.6 384.0 76.4 0 0.000 0 0.000 0.000
10240.0 10240.0 0.0 0.0 81920.0 4984.1 102400.0 0.0 4480.0 786.6 384.0 76.4 0 0.000 0 0.000 0.000
10240.0 10240.0 0.0 0.0 81920.0 25464.1 102400.0 0.0 4480.0 786.6 384.0 76.4 0 0.000 0 0.000 0.000
10240.0 10240.0 0.0 0.0 81920.0 45944.2 102400.0 0.0 4480.0 786.6 384.0 76.4 0 0.000 0 0.000 0.000
10240.0 10240.0 0.0 0.0 81920.0 66424.2 102400.0 0.0 4480.0 786.6 384.0 76.4 0 0.000 0 0.000 0.000
10240.0 10240.0 0.0 713.8 81920.0 11878.4 102400.0 10242.1 4864.0 3043.9 512.0 321.7 1 0.005 0 0.000 0.005
10240.0 10240.0 0.0 713.8 81920.0 33961.9 102400.0 10242.1 4864.0 3043.9 512.0 321.7 1 0.005 0 0.000 0.005
10240.0 10240.0 0.0 713.8 81920.0 54441.9 102400.0 10242.1 4864.0 3043.9 512.0 321.7 1 0.005 0 0.000 0.005
10240.0 10240.0 0.0 713.8 81920.0 74922.0 102400.0 10242.1 4864.0 3043.9 512.0 321.7 1 0.005 0 0.000 0.005
10240.0 10240.0 828.5 0.0 81920.0 22891.3 102400.0 10242.1 4864.0 3044.4 512.0 321.7 2 0.005 0 0.000 0.005
10240.0 10240.0 828.5 0.0 81920.0 43371.3 102400.0 10242.1 4864.0 3044.4 512.0 321.7 2 0.005 0 0.000 0.005
10240.0 10240.0 828.5 0.0 81920.0 63851.3 102400.0 10242.1 4864.0 3044.4 512.0 321.7 2 0.005 0 0.000 0.005
10240.0 10240.0 0.0 964.7 81920.0 10240.0 102400.0 20484.1 4864.0 3044.5 512.0 321.7 3 0.010 0 0.000 0.010
10240.0 10240.0 0.0 964.7 81920.0 32230.5 102400.0 20484.1 4864.0 3044.5 512.0 321.7 3 0.010 0 0.000 0.010
10240.0 10240.0 0.0 964.7 81920.0 52710.5 102400.0 20484.1 4864.0 3044.5 512.0 321.7 3 0.010 0 0.000 0.010
10240.0 10240.0 0.0 964.7 81920.0 73190.5 102400.0 20484.1 4864.0 3044.5 512.0 321.7 3 0.010 0 0.000 0.010
10240.0 10240.0 759.6 0.0 81920.0 22035.2 102400.0 20484.1 4864.0 3044.5 512.0 321.7 4 0.011 0 0.000 0.011
10240.0 10240.0 759.6 0.0 81920.0 42515.2 102400.0 20484.1 4864.0 3044.5 512.0 321.7 4 0.011 0 0.000 0.011
10240.0 10240.0 759.6 0.0 81920.0 62995.2 102400.0 20484.1 4864.0 3044.5 512.0 321.7 4 0.011 0 0.000 0.011
10240.0 10240.0 0.0 886.2 81920.0 10240.0 102400.0 30724.1 4864.0 3044.5 512.0 321.7 5 0.016 0 0.000 0.016
10240.0 10240.0 0.0 886.2 81920.0 32212.1 102400.0 30724.1 4864.0 3044.5 512.0 321.7 5 0.016 0 0.000 0.016
10240.0 10240.0 0.0 886.2 81920.0 52692.1 102400.0 30724.1 4864.0 3044.5 512.0 321.7 5 0.016 0 0.000 0.016
10240.0 10240.0 0.0 886.2 81920.0 73172.1 102400.0 30724.1 4864.0 3044.5 512.0 321.7 5 0.016 0 0.000 0.016
10240.0 10240.0 0.0 0.0 81920.0 22023.2 102400.0 31373.1 4864.0 3044.5 512.0 321.7 6 0.019 0 0.000 0.019
10240.0 10240.0 0.0 0.0 81920.0 42503.3 102400.0 31373.1 4864.0 3044.5 512.0 321.7 6 0.019 0 0.000 0.019
10240.0 10240.0 0.0 0.0 81920.0 62983.3 102400.0 31373.1 4864.0 3044.5 512.0 321.7 6 0.019 0 0.000 0.019
10240.0 10240.0 0.0 0.0 81920.0 10240.0 102400.0 41613.1 4864.0 3044.5 512.0 321.7 7 0.024 0 0.000 0.024
10240.0 10240.0 0.0 0.0 81920.0 32204.4 102400.0 41613.1 4864.0 3044.5 512.0 321.7 7 0.024 0 0.000 0.024
10240.0 10240.0 0.0 0.0 81920.0 52684.4 102400.0 41613.1 4864.0 3044.5 512.0 321.7 7 0.024 0 0.000 0.024
10240.0 10240.0 0.0 0.0 81920.0 73164.4 102400.0 41613.1 4864.0 3044.5 512.0 321.7 7 0.024 0 0.000 0.024
10240.0 10240.0 0.0 0.0 81920.0 22018.2 102400.0 41613.1 4864.0 3044.9 512.0 321.7 8 0.025 0 0.000 0.025
10240.0 10240.0 0.0 0.0 81920.0 42498.3 102400.0 41613.1 4864.0 3044.9 512.0 321.7 8 0.025 0 0.000 0.025
10240.0 10240.0 0.0 0.0 81920.0 62978.3 102400.0 41613.1 4864.0 3044.9 512.0 321.7 8 0.025 0 0.000 0.025
10240.0 10240.0 0.0 0.0 81920.0 10240.0 102400.0 51853.2 4864.0 3044.9 512.0 321.7 9 0.030 0 0.000 0.030
10240.0 10240.0 0.0 0.0 81920.0 32201.1 102400.0 51853.2 4864.0 3044.9 512.0 321.7 9 0.030 0 0.000 0.030
10240.0 10240.0 0.0 0.0 81920.0 52681.2 102400.0 51853.2 4864.0 3044.9 512.0 321.7 9 0.030 0 0.000 0.030
10240.0 10240.0 0.0 0.0 81920.0 73161.2 102400.0 51853.2 4864.0 3044.9 512.0 321.7 9 0.030 0 0.000 0.030
10240.0 10240.0 0.0 0.0 81920.0 22016.1 102400.0 51853.2 4864.0 3045.6 512.0 321.7 10 0.031 0 0.000 0.031
10240.0 10240.0 0.0 0.0 81920.0 42496.2 102400.0 51853.2 4864.0 3045.6 512.0 321.7 10 0.031 0 0.000 0.031
10240.0 10240.0 0.0 0.0 81920.0 62976.2 102400.0 51853.2 4864.0 3045.6 512.0 321.7 10 0.031 0 0.000 0.031
10240.0 10240.0 0.0 0.0 81920.0 10240.0 102400.0 62093.2 4864.0 3045.9 512.0 321.7 11 0.036 0 0.000 0.036
10240.0 10240.0 0.0 0.0 81920.0 32199.8 102400.0 62093.2 4864.0 3045.9 512.0 321.7 11 0.036 0 0.000 0.036
10240.0 10240.0 0.0 0.0 81920.0 52679.8 102400.0 62093.2 4864.0 3045.9 512.0 321.7 11 0.036 0 0.000 0.036
10240.0 10240.0 0.0 0.0 81920.0 52679.8 102400.0 62093.2 4864.0 3045.9 512.0 321.7 11 0.036 0 0.000 0.036
10240.0 10240.0 0.0 0.0 81920.0 22015.3 102400.0 62093.2 4864.0 3045.9 512.0 321.7 12 0.036 0 0.000 0.036
10240.0 10240.0 0.0 0.0 81920.0 42495.3 102400.0 62093.2 4864.0 3045.9 512.0 321.7 12 0.036 0 0.000 0.036
10240.0 10240.0 0.0 0.0 81920.0 62975.3 102400.0 62093.2 4864.0 3045.9 512.0 321.7 12 0.036 0 0.000 0.036
10240.0 10240.0 0.0 0.0 81920.0 0.0 102400.0 72333.2 4864.0 3045.9 512.0 321.7 13 0.041 0 0.000 0.041
10240.0 10240.0 0.0 0.0 81920.0 32199.2 102400.0 72333.2 4864.0 3045.9 512.0 321.7 13 0.041 0 0.000 0.041
10240.0 10240.0 0.0 0.0 81920.0 52679.3 102400.0 72333.2 4864.0 3045.9 512.0 321.7 13 0.041 0 0.000 0.041
10240.0 10240.0 0.0 0.0 81920.0 74797.7 102400.0 72333.2 4864.0 3045.9 512.0 321.7 13 0.041 0 0.000 0.041
10240.0 10240.0 4.0 0.0 81920.0 0.0 102400.0 72333.2 4864.0 3048.0 512.0 321.7 14 0.042 0 0.000 0.042
10240.0 10240.0 4.0 0.0 81920.0 22014.9 102400.0 72333.2 4864.0 3048.0 512.0 321.7 14 0.042 0 0.000 0.042
10240.0 10240.0 4.0 0.0 81920.0 62975.0 102400.0 72333.2 4864.0 3048.0 512.0 321.7 14 0.042 0 0.000 0.042
10240.0 10240.0 0.0 2.0 81920.0 10240.0 102400.0 82573.2 4864.0 3048.0 512.0 321.7 15 0.047 0 0.000 0.047
10240.0 10240.0 0.0 2.0 81920.0 10240.0 102400.0 82573.2 4864.0 3048.0 512.0 321.7 15 0.047 0 0.000 0.047
10240.0 10240.0 0.0 2.0 81920.0 32199.0 102400.0 82573.2 4864.0 3048.0 512.0 321.7 15 0.047 0 0.000 0.047
10240.0 10240.0 0.0 2.0 81920.0 73159.1 102400.0 82573.2 4864.0 3048.0 512.0 321.7 15 0.047 0 0.000 0.047
10240.0 10240.0 4.0 0.0 81920.0 0.0 102400.0 82573.2 4864.0 3048.0 512.0 321.7 16 0.048 0 0.000 0.048
10240.0 10240.0 4.0 0.0 81920.0 22014.7 102400.0 82573.2 4864.0 3048.0 512.0 321.7 16 0.048 0 0.000 0.048
10240.0 10240.0 4.0 0.0 81920.0 62974.8 102400.0 82573.2 4864.0 3048.0 512.0 321.7 16 0.048 0 0.000 0.048
10240.0 10240.0 0.0 2.0 81920.0 10240.0 102400.0 10892.1 4864.0 3048.7 512.0 321.7 17 0.053 2 0.002 0.055
10240.0 10240.0 0.0 2.0 81920.0 10240.0 102400.0 10892.1 4864.0 3048.7 512.0 321.7 17 0.053 2 0.002 0.055
10240.0 10240.0 0.0 2.0 81920.0 32198.9 102400.0 10892.1 4864.0 3048.7 512.0 321.7 17 0.053 2 0.002 0.055
10240.0 10240.0 0.0 2.0 81920.0 73159.0 102400.0 10892.1 4864.0 3048.7 512.0 321.7 17 0.053 2 0.002 0.055
10240.0 10240.0 2.0 0.0 81920.0 22833.9 102400.0 10892.1 4864.0 3049.0 512.0 321.7 18 0.054 2 0.002 0.056
10240.0 10240.0 2.0 0.0 81920.0 22833.9 102400.0 10892.1 4864.0 3049.0 512.0 321.7 18 0.054 2 0.002 0.056
10240.0 10240.0 2.0 0.0 81920.0 43313.9 102400.0 10892.1 4864.0 3049.0 512.0 321.7 18 0.054 2 0.002 0.056
10240.0 10240.0 0.0 4.0 81920.0 0.0 102400.0 21132.1 4864.0 3049.0 512.0 321.7 19 0.056 2 0.002 0.058
10240.0 10240.0 0.0 4.0 81920.0 10240.0 102400.0 21132.1 4864.0 3049.0 512.0 321.7 19 0.056 2 0.002 0.058
10240.0 10240.0 0.0 4.0 81920.0 32193.5 102400.0 21132.1 4864.0 3049.0 512.0 321.7 19 0.056 2 0.002 0.058
10240.0 10240.0 0.0 4.0 81920.0 52673.6 102400.0 21132.1 4864.0 3049.0 512.0 321.7 19 0.056 2 0.002 0.058
10240.0 10240.0 2.0 0.0 81920.0 22011.2 102400.0 21132.1 4864.0 3049.0 512.0 321.7 20 0.057 2 0.002 0.058
10240.0 10240.0 2.0 0.0 81920.0 22011.2 102400.0 21132.1 4864.0 3049.0 512.0 321.7 20 0.057 2 0.002 0.058
10240.0 10240.0 2.0 0.0 81920.0 42491.2 102400.0 21132.1 4864.0 3049.0 512.0 321.7 20 0.057 2 0.002 0.058
10240.0 10240.0 0.0 4.0 81920.0 0.0 102400.0 31372.2 4864.0 3049.0 512.0 321.7 21 0.059 2 0.002 0.060
10240.0 10240.0 0.0 4.0 81920.0 10240.0 102400.0 31372.2 4864.0 3049.0 512.0 321.7 21 0.059 2 0.002 0.060
10240.0 10240.0 0.0 4.0 81920.0 32196.6 102400.0 31372.2 4864.0 3049.0 512.0 321.7 21 0.059 2 0.002 0.060
10240.0 10240.0 0.0 4.0 81920.0 52676.6 102400.0 31372.2 4864.0 3049.0 512.0 321.7 21 0.059 2 0.002 0.060
10240.0 10240.0 4.0 0.0 81920.0 0.0 102400.0 31372.2 4864.0 3049.0 512.0 321.7 22 0.059 2 0.002 0.061
10240.0 10240.0 4.0 0.0 81920.0 22013.2 102400.0 31372.2 4864.0 3049.0 512.0 321.7 22 0.059 2 0.002 0.061
10240.0 10240.0 4.0 0.0 81920.0 42493.2 102400.0 31372.2 4864.0 3049.0 512.0 321.7 22 0.059 2 0.002 0.061
10240.0 10240.0 0.0 4.0 81920.0 0.0 102400.0 41612.2 4864.0 3049.0 512.0 321.7 23 0.061 2 0.002 0.062
10240.0 10240.0 0.0 4.0 81920.0 10240.0 102400.0 41612.2 4864.0 3049.0 512.0 321.7 23 0.061 2 0.002 0.062
10240.0 10240.0 0.0 4.0 81920.0 32197.9 102400.0 41612.2 4864.0 3049.0 512.0 321.7 23 0.061 2 0.002 0.062
10240.0 10240.0 0.0 4.0 81920.0 52677.9 102400.0 41612.2 4864.0 3049.0 512.0 321.7 23 0.061 2 0.002 0.062
10240.0 10240.0 0.0 0.0 81920.0 0.0 102400.0 41612.2 4864.0 3049.0 512.0 321.7 24 0.061 2 0.002 0.063
10240.0 10240.0 0.0 0.0 81920.0 42494.1 102400.0 41612.2 4864.0 3049.0 512.0 321.7 24 0.061 2 0.002 0.063
10240.0 10240.0 0.0 0.0 81920.0 42494.1 102400.0 41612.2 4864.0 3049.0 512.0 321.7 24 0.061 2 0.002 0.063
10240.0 10240.0 0.0 0.0 81920.0 0.0 102400.0 51852.3 4864.0 3049.8 512.0 321.7 25 0.063 2 0.002 0.065
10240.0 10240.0 0.0 0.0 81920.0 10240.0 102400.0 51852.3 4864.0 3049.8 512.0 321.7 25 0.063 2 0.002 0.065
10240.0 10240.0 0.0 0.0 81920.0 32198.4 102400.0 51852.3 4864.0 3049.8 512.0 321.7 25 0.063 2 0.002 0.065
10240.0 10240.0 0.0 0.0 81920.0 52678.5 102400.0 51852.3 4864.0 3049.8 512.0 321.7 25 0.063 2 0.002 0.065
10240.0 10240.0 0.0 0.0 81920.0 0.0 102400.0 51852.3 4864.0 3049.8 512.0 321.7 26 0.064 2 0.002 0.065
10240.0 10240.0 0.0 0.0 81920.0 22014.4 102400.0 51852.3 4864.0 3049.8 512.0 321.7 26 0.064 2 0.002 0.065
10240.0 10240.0 0.0 0.0 81920.0 42494.4 102400.0 51852.3 4864.0 3049.8 512.0 321.7 26 0.064 2 0.002 0.065
10240.0 10240.0 0.0 0.0 81920.0 62974.4 102400.0 51852.3 4864.0 3049.8 512.0 321.7 26 0.064 2 0.002 0.065
10240.0 10240.0 0.0 0.0 81920.0 10240.0 102400.0 62092.3 4864.0 3049.8 512.0 321.7 27 0.065 2 0.002 0.067
10240.0 10240.0 0.0 0.0 81920.0 32198.7 102400.0 62092.3 4864.0 3049.8 512.0 321.7 27 0.065 2 0.002 0.067
10240.0 10240.0 0.0 0.0 81920.0 52678.7 102400.0 62092.3 4864.0 3049.8 512.0 321.7 27 0.065 2 0.002 0.067
10240.0 10240.0 0.0 0.0 81920.0 73158.7 102400.0 62092.3 4864.0 3049.8 512.0 321.7 27 0.065 2 0.002 0.067
10240.0 10240.0 0.0 0.0 81920.0 22014.5 102400.0 62092.3 4864.0 3050.1 512.0 321.7 28 0.066 2 0.002 0.068
10240.0 10240.0 0.0 0.0 81920.0 42494.6 102400.0 62092.3 4864.0 3050.1 512.0 321.7 28 0.066 2 0.002 0.068
首先從前面幾行可以看出內(nèi)存各個(gè)區(qū)域的大小十酣,Survivor0/Survivor1 10M涩拙,Eden區(qū)80M枣宫,老年代100M 等信息。
2吃环、新生代對象增長的速率
從 EU 這一行可以看出也颤,新生代基本是按照每秒20M左右的對象在增長。
3郁轻、YoungGC的觸發(fā)頻率和耗時(shí)
從 Eden 區(qū)的內(nèi)存變化可以看出翅娶,基本是每隔3秒或4秒就會觸發(fā)一次 YoungGC,比如第一次Eden區(qū)增長到66424.2時(shí)好唯,經(jīng)過一次 YoungGC后只剩下11878.4竭沫。從 YGC 這列也大致可以看出YoungGC的頻率。
從 YGCT 這列可以看出骑篙,每次YoungGC耗時(shí)15毫秒的樣子蜕提,也就是說每隔34秒,觸發(fā)一次YoungGC靶端,一次YoungGC系統(tǒng)卡頓1~5毫秒谎势。這也可以看出 YoungGC 其實(shí)是很快的,就算新生代800M也才10幾毫秒杨名,對系統(tǒng)幾乎沒什么影響脏榆。
4、YoungGC后存活對象大小以及有多少對象進(jìn)入了老年代
從 S0U台谍、S1U 這兩列的變化可以看出须喂,每次YoungGC后有800K左右的對象進(jìn)入 Survivor 區(qū)。
從 OU 這列的變化可以看出趁蕊,每次進(jìn)入老年代的對象在10M左右坞生,所以一次YoungGC后可能有10M的存活對象進(jìn)入老年代。
5掷伙、FullGC的觸發(fā)頻率和耗時(shí)
從 OU 這列的變化可以看出是己,在老年代達(dá)到 82573.2 時(shí),觸發(fā)了 FullGC炎咖,回收后老年代大小為 10892.1赃泡。從整體的時(shí)間線上看,剛好60秒就觸發(fā)了一次FullGC乘盼。
從 FGCT 可以看出升熊,一次FullGC 耗時(shí)2毫秒,
為什么在老年代 82573.2 時(shí)就觸發(fā)了FullGC呢绸栅,我們從GC日志中來看:
可以看出這一秒內(nèi)级野,實(shí)際上這次YoungGC導(dǎo)致有10M的對象進(jìn)入老年代,老年代實(shí)際有92815K對象,因而應(yīng)該是CMS超過了 92% 的閥值之后觸發(fā)了老年代GC蓖柔。
6辰企、使用GCeasy查看GC日志
至此,其實(shí)已經(jīng)基本上分析出整個(gè)JVM的運(yùn)轉(zhuǎn)情況了况鸣。這里總結(jié)下:
- 新生代牢贸、老年代 100M,Eden區(qū)80M镐捧,Survivor區(qū)10M潜索;
- Eden區(qū)每秒產(chǎn)生20M左右對象,每隔3~4秒觸發(fā)一次YoungGC懂酱;
- YoungGC后存活對象在0~10M左右竹习,由于無法放入Survivor區(qū)會進(jìn)入老年代,每次進(jìn)入老年代對象10M左右列牺;
- 在經(jīng)過16次左右YoungGC后整陌,也就是60秒左右老年代會接近占滿,超過設(shè)置的閥值瞎领,觸發(fā)一次 FullGC泌辫。
從上面的分析可以看出,jstat 監(jiān)控的輸出結(jié)果基本是符合前面估算的結(jié)果的默刚。但是粗略估算需要熟悉系統(tǒng)核心的業(yè)務(wù)甥郑,而且其它未知因素也比較多,粗略估算一般用于系統(tǒng)剛上線階段來設(shè)置JVM參數(shù)荤西。而通過 jstat 來監(jiān)控一般就可以比較準(zhǔn)確的摸清JVM的運(yùn)行情況,然后進(jìn)行性能調(diào)優(yōu)伍俘。
接下來再通過GC日志來看下是否符合分析的情況邪锌,GC日志就不再一行一行分析了,我們直接通過在線工具 GCeasy 來看看內(nèi)存變化和GC的情況癌瘾。將輸出的GC日志直接拷貝到 GCeasy 上觅丰,就可以看到分析的結(jié)果。
1)GC總體情況
從這張圖可以得到如下信息:
- 垃圾回收器的吞吐率為 99.937%妨退;
- 平均GC停頓時(shí)間:2毫秒
- 最長GC停頓時(shí)間:10毫秒
- 80% 的GC耗時(shí) 0~1毫秒
- 20% 的GC耗時(shí) 9~10毫秒
2)YoungGC頻率
從 Young Gen 這個(gè)統(tǒng)計(jì)圖可以看出妇萄,YoungGC的頻率在3~4秒的樣子。
3)老年代GC頻率
從這張統(tǒng)計(jì)圖可以看出咬荷,老年代是每兩次YoungGC增長一次冠句,每次增長10M左右,在60秒左右觸發(fā)一次OldGC幸乒。
4)CMS回收情況
這張圖展示了CMS各個(gè)階段的統(tǒng)計(jì)情況
7懦底、性能優(yōu)化
從上面的分析可以看出,這個(gè)JVM最大的問題在于 Survivor 區(qū)沒起作用罕扎,Survivor 區(qū)只有10M聚唐,而YoungGC后存活對象大于10M丐重,導(dǎo)致無法放進(jìn)Survivor區(qū)而直接進(jìn)入老年代了,進(jìn)而觸發(fā)FullGC杆查。因此我們可以增大新生代大小或者調(diào)整Eden區(qū)和Survivor區(qū)比例扮惦,來讓對象進(jìn)入Survivor區(qū)。
比如改為如下配置:新生代給150M亲桦,比例調(diào)整為6崖蜜,這樣Eden區(qū)90M,Survivor區(qū)各30M烙肺,這樣Survivor區(qū)足以放下YoungGC后存活的對象纳猪,也基本上能避免動(dòng)態(tài)年齡判斷導(dǎo)致對象進(jìn)入老年代。
-Xms200M
-Xmx200M
-Xmn150M
-XX:SurvivorRatio=6
再看這時(shí)的GC情況桃笙,首先從 S0U氏堤、S1U 的變化可以看出,Survivor 區(qū)在起作用了搏明,每次YoungGC后存活對象都進(jìn)入Survivor區(qū)了鼠锈。
然后從 OU、YGC的變化可以看出星著,有部分長期存活的對象在YoungGC次數(shù)超過設(shè)置的GC年齡閥值(設(shè)置的5歲)后购笆,進(jìn)入了老年代。
從 FGC 這列可以看出虚循,Survivor 區(qū)合理設(shè)置后同欠,再?zèng)]有發(fā)生過 FullGC 了。
五横缔、使用 MAT 分析OOM問題
對于排查 OOM 問題铺遂、分析程序堆內(nèi)存使用情況,最好的方式就是分析堆轉(zhuǎn)儲茎刚,堆轉(zhuǎn)儲襟锐,包含了堆現(xiàn)場全貌和線程棧信息。這節(jié)就來看看如何使用MAT分析OOM問題膛锭。
1粮坞、運(yùn)行示例程序
準(zhǔn)備如下兩個(gè)測試類:
package com.lyyzoo.test.jvm;
public class OomService {
private List<String> data = new ArrayList<>();
public void addData() {
//往同一個(gè)ArrayList中不斷加入大小為10KB的字符串
data.add(IntStream.rangeClosed(1, 10_000)
.mapToObj(__ -> "A")
.collect(Collectors.joining("")));
}
}
package com.lyyzoo.test.jvm;
public class OomMain {
public static void main(String[] args) {
OomService service = new OomService();
while (true) {
service.addData();
}
}
}
設(shè)置如下JVM參數(shù):
-Xms200M
-Xmx200M
-Xmn100M
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./dump.hprof
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:./gc.log
運(yùn)行程序后報(bào)OOM異常:
2、MAT 分析OOM問題的思路
對于線上運(yùn)行的程序初狰,如果我們不能通過日志快速定位出OOM的根源莫杈,一般就可以使用MAT來分析OOM的問題。
使用 MAT 分析 OOM 問題跷究,一般可以按照以下思路進(jìn)行:
- 通過支配樹功能或直方圖功能查看消耗內(nèi)存最大的類型姓迅,來分析內(nèi)存泄露的大概原因;
- 查看那些消耗內(nèi)存最大的類型、詳細(xì)的對象明細(xì)列表丁存,以及它們的引用鏈肩杈,來定位內(nèi)存泄露的具體點(diǎn);
- 配合查看對象屬性的功能解寝,可以脫離源碼看到對象的各種屬性的值和依賴關(guān)系扩然,幫助我們理清程序邏輯和參數(shù);
- 輔助使用查看線程棧來看 OOM 問題是否和過多線程有關(guān)聋伦,甚至可以在線程椃蚺迹看到 OOM 最后一刻出現(xiàn)異常的線程。
如果dump出來的內(nèi)存快照很大觉增,比如有幾個(gè)G兵拢,務(wù)必在啟動(dòng)MAT之前,先在配置文件(MemoryAnalyzer.ini)里給MAT本身設(shè)置—下堆內(nèi)存大杏饨浮(默認(rèn)為1024m)说铃,比如設(shè)置為4個(gè)G,或者8個(gè)G嘹履。
3腻扇、總覽圖 — 快速分析OOM問題
使用MAT打開堆轉(zhuǎn)儲文件 dump.hprof,打開后先進(jìn)入的是概覽信息界面:
從餅圖可以看出砾嫉,明顯有對象占用了大量內(nèi)存幼苛,然后再看 Problem Suspect1,已經(jīng)說明了 main 線程通過局部變量占據(jù)了 99.42% 內(nèi)存的對象焕刮,而且是 java.lang.Object[] 數(shù)組占據(jù)了大量內(nèi)存舶沿。
點(diǎn)擊 Details 進(jìn)去查看詳細(xì)的說明,從 “Accumulated Objects in Dominator Tree” 支配樹可以看出配并,main 線程引用了 OomService 對象暑椰,OomService 引用了一個(gè) ArrayList 對象,然后 ArrayList 存儲了大量 String 對象荐绝。這里基本上就能分析出OOM的根源了。
再點(diǎn)擊 See stacktrace 看看線程棻芟基本就能定位到問題代碼了低滩。
4、直方圖 — 定位根源
工具欄的第二個(gè)按鈕可以打開直方圖岩喷,直方圖按照類型進(jìn)行分組恕沫,列出了每個(gè)類有多少個(gè)實(shí)例,以及占用的內(nèi)存纱意。
可以看到婶溯,char[] 字節(jié)數(shù)組占用內(nèi)存最多,對象數(shù)量也很多,第二位的 String 對象數(shù)量也非常多迄委,有 9791 個(gè)褐筛,從這大概可以猜出應(yīng)該是創(chuàng)建了大量的 String 對象。
在 char[] 上點(diǎn)擊右鍵叙身,選擇 List objects -> with incoming references渔扎,就可以列出所有的 char[] 實(shí)例,以及每個(gè) char[] 的整個(gè)引用關(guān)系鏈:
隨機(jī)展開一個(gè) char[]信轿,如下圖所示:
右側(cè)框中可以看到整個(gè)引用鏈,左側(cè)的框可以查看每一個(gè)實(shí)例的內(nèi)部屬性。
通過這個(gè)引用鏈可以發(fā)現(xiàn)是 String 對象引用了 char[] 數(shù)組(String 的內(nèi)部結(jié)構(gòu)就是一個(gè) char[] 數(shù)組)总放,說明創(chuàng)建了大量的 String 對象陕见;然后 String 對象又被 ArrayList 的 Object[] 數(shù)組引用著,說明是大量 String 對象放入了 ArrayList 中即彪,然后這個(gè) ArrayList 又被 OomService 的 data 變量引用著紧唱。到這里就定位出了引發(fā)OOM的類了。
Retained Heap(深堆)代表對象本身和對象關(guān)聯(lián)的對象占用的內(nèi)存祖凫,Shallow Heap(淺堆)代表對象本身占用的內(nèi)存琼蚯。比如,OomService 中的 data 這個(gè) ArrayList 對象本身只有 16 字節(jié)惠况,但是其所有關(guān)聯(lián)的對象占用了 130+MB 內(nèi)存遭庶。
如果希望看到完整內(nèi)容的話,可以右鍵選擇 Copy->Value稠屠,把值復(fù)制到剪貼板或保存到文件中:
5峦睡、支配樹 — 定位根源
其實(shí),使用直方圖定位 OomService权埠,已經(jīng)走了些彎路榨了。可以點(diǎn)擊工具欄中第三個(gè)按鈕進(jìn)入支配樹界面攘蔽。這個(gè)界面會按照對象保留的 Retained Heap 倒序直接列出占用內(nèi)存最大的對象龙屉。
可以看到,第一位就是 OomService满俗,整個(gè)路徑是 OomSerice -> ArrayList -> Object[] -> String -> char[] 转捕。
6、線程棧 — 分析代碼
可以點(diǎn)擊工具欄的第五個(gè)按鈕唆垃,打開線程視圖來分析 OomService 執(zhí)行什么邏輯五芝。可以看到 OomService 是 OomMain 的一個(gè)本地變量辕万,然后 OomMain 調(diào)用了 OomService 的 addData 方法枢步,然后 addData 方法里應(yīng)該是通過 Stream 生成一個(gè)字符串放入 data 中的沉删。
7、OQL—查詢數(shù)據(jù)
點(diǎn)擊工具欄的第四個(gè)按鈕醉途,來到 OQL 界面矾瑰。在這個(gè)界面,我們可以使用類似 SQL 的語法结蟋,在 dump 中搜索數(shù)據(jù)脯倚。可以看到只創(chuàng)建了一個(gè) OomService 實(shí)例嵌屎,說明只有一個(gè)地方調(diào)用了 OomService 的方法推正。
然后可通過 List objects 功能搜索引用OomService 的對象:
可以看到其被 main 線程引用著:
六、使用 Arthas 分析高 CPU 問題
Arthas 是阿里開源的 Java 診斷工具宝惰,相比 JDK 內(nèi)置的診斷工具植榕,要更人性化,并且功能強(qiáng)大尼夺,可以實(shí)現(xiàn)許多問題的一鍵定位尊残,而且可以一鍵反編譯類查看源碼,甚至是直接進(jìn)行生產(chǎn)代碼熱修復(fù)淤堵,實(shí)現(xiàn)在一個(gè)工具內(nèi)快速定位和修復(fù)問題的一站式服務(wù)寝衫。
Arthas 官方文檔:https://alibaba.github.io/arthas/
1、運(yùn)行示例程序
準(zhǔn)備如下導(dǎo)致CPU負(fù)載高的代碼:代碼中創(chuàng)建了2個(gè)線程的線程池拐邪,提交的任務(wù)通過 BCryptPasswordEncoder 對一個(gè)長字符串加密慰毅,這個(gè)是非常消耗CPU的。
package com.lyyzoo.test.jvm;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class CpuService {
private BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
private ExecutorService executor = Executors.newFixedThreadPool(2);
public void doTask() throws Exception {
while (true) {
randomEncode(RandomUtils.nextInt(0, 10000));
}
}
private void randomEncode(Integer size) throws Exception {
String payload = IntStream.rangeClosed(1, size).mapToObj(__ -> "A").collect(Collectors.joining());
Future<String> f1 = executor.submit(() -> {
return encoder.encode(payload);
});
Future<String> f2 = executor.submit(() -> {
return encoder.encode(payload);
});
f1.get();
f2.get();
}
}
public class CpuMain {
private static CpuService service = new CpuService();
public static void main(String[] args) throws Exception {
service.doTask();
}
}
2扎阶、啟動(dòng) Arthas
首先汹胃,下載 Arthas:https://arthas.aliyun.com/arthas-boot.jar
然后把程序先運(yùn)行起來,再運(yùn)行 arthas:java -jar arthas-boot.jar
啟動(dòng)后东臀,直接找到我們要排查的 JVM 進(jìn)程着饥,然后可以看到 Arthas 附加進(jìn)程成功:
輸入 help 命令,可以看到所有支持的命令列表惰赋。這里主要會用到 dashboard宰掉、thread、jad赁濒、watch 等命令贵扰,來定位高CPU的問題。
3流部、dashboard — 展示整體情況
dashboard 命令整體展示了進(jìn)程所有線程、內(nèi)存纹坐、GC 等情況枝冀,可以明顯看到兩個(gè)CPU占用很高的線程舞丛,從線程名字來看應(yīng)該是線程池的線程。
4果漾、thread — 查看高CPU的線程
接下來球切,查看最繁忙的線程在執(zhí)行的線程棧,可以使用 thread -n 命令绒障。這里吨凑,我們查看下最忙的 2 個(gè)線程:從線程棧可以看出户辱,應(yīng)該就是 CpuService 的 randomEncode 方法調(diào)用 BCryptPasswordEncoder 的 encode 方法導(dǎo)致CPU負(fù)載高的鸵钝。
5、watch — 監(jiān)控參數(shù)
如果想要觀察方法的入?yún)⒑统鰠⒙洌梢杂?watch 命令來觀察:
6恩商、jad — 反編譯
前面已經(jīng)分析出CPU負(fù)載高的位置是 CpuService 的 randomEncode 了,那么通過 jad 反編譯來看看源碼長什么樣子必逆,方便我們進(jìn)一步定位問題怠堪。
7、redefine — 重載類
如果我們想做線上調(diào)試名眉,又不想在本地改代碼粟矿,打印日志,再提交到服務(wù)器损拢,再重啟服務(wù)測試陌粹,那我們可以結(jié)合 arthas 的 jad、mc探橱、redefine 來動(dòng)態(tài)重定義類申屹。
① 首先用 jad 把源文件下載下來
然后修改下源碼:添加了一行輸出日志
② 使用 mc 命令反編譯源文件
反編譯后會生成對應(yīng)的 class 文件:
③ 使用 redefine 重載類
就可以看到控制臺已經(jīng)在輸出我們打印的日志了:
需要額外說明的是,由于 monitor隧膏、trace哗讥、watch 等命令是通過字節(jié)碼增強(qiáng)技術(shù)來實(shí)現(xiàn)的,會在指定類的方法中插入一些切面來實(shí)現(xiàn)數(shù)據(jù)統(tǒng)計(jì)和觀測胞枕,因此診斷結(jié)束要執(zhí)行 shutdown 來還原類或方法字節(jié)碼杆煞,然后退出 Arthas。