文/朱季謙
最近做了一些JVM監控與調優的事情,算是第一次實踐,還比較陌生,故而先把這一次經驗簡單記下筆記,這樣,對后面學習調優方面時,不至於又想不起來了。
本文檔主要總結在window本地環境遠程對linux服務斷的JVM虛擬機進行監控與調優的方法。
visualvm工具是JDK自帶的,在java安裝目錄下可以找到:C:\Program Files\Java\jdk1.8.0_77\bin
打開visualvm工具,右擊遠程,添加遠程主機——
在彈出框上的主機名處,填寫需要連接的服務器IP——
添加成功后,右邊框就出現了以下圖標——
這時,右擊“42.194.xx.xx”,會看到,有兩種遠程連接方式,一個是JMX,一個是jstatd。
這里主要分享是以jstatd模式。在以jstatd模式連接前,需要在監控的遠程服務端啟動jstatd,啟動步驟如下——
-
找到服務端jdk的bin目錄,新建jstatd.all.policy文件
[root@VM-16-4-centos bin]# whereis java java: /usr/bin/java [root@VM-16-4-centos bin]# cd /usr/bin/ [root@VM-16-4-centos bin]# vim jstatd.all.policy
將以下內容復制到jstatd.all.policy文件里——
grant codebase "file:${java.home}/../lib/tools.jar" { permission java.security.AllPermission; };
保存,設置權限——
[root@VM-16-4-centos bin]# chmod 777 jstatd.all.policy
設置成功——
-
在監控的遠程服務端啟動jstatd
執行 jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=42.194.xxx.xx &
注:42.194.xxx.xx 是我個人騰訊雲機器IP。
[root@VM-16-4-centos bin]# jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=42.194.xxx.xx & [1] 52056
這時,觀察visualvm工具右邊欄菜單,可以看到遠程連接的服務端已經自動連接上jstatd,這就意味着,可以在本地通過遠程jstatd來監控開發服務器上的jvm信息了,從而進行jvm調優監控等操作。
點擊其中一個進程biz-0.0.1-SNZPSHOT.jar,就可以進入到對應的監控台——
visualvm控制台有概述、監視、抽樣器等菜單,同時,可以裝入插件擴展功能——
-
概述
可以看到jvm參數、系統屬性、jdk版本與安裝路徑等信息;
設置的jvm參數,這里新生代分配了最小堆空間是256m,最大堆空間是256m,新生代128m,元空間是128m,堆=新生代+老年代,不包括永久代(方法區),這就意味着,這配置當中的老年代=256m。
-Xms256M -Xmx256M -Xmn128M -XX:PermSize=128M -XX:MaxPermSize=256M
這些都是指定JVM參數運行對應的jar進程,除此之外,還有其他參數可做設置。
-
監視
監控模塊可以實時看到進程所在的堆、元空間、類及線程的報表數據監控,其中,堆和元空間的報表對調優可以起到很有用的幫助。
-
除了visualvm自帶的功能外,我們需要裝入一個實時監控GC的插件visualgc,這個插件很方便對JVM做監控與調優。
考慮到visualgc插件通過官網下載很慢,我已經保存在網盤當中,可直接通過網盤進行下載——
鏈接:https://pan.baidu.com/s/17TSf0ZdFtMdfog6xzbj3ZQ 提取碼:el6c
插件裝載方式,右擊工具欄,選擇插件——
彈出框后,點“已下載”按鈕,再點擊“添加插件”將需要安裝的visualgc插件添加進來——
安裝成功后,重啟一下visualvm,就可以看到菜單欄上多出一個Visual GC插件——
Visual gc 工具分成布局分成三部分,可在右上角對應方框里勾選【Space】【Graphs】【Histogram】,它們各自的作用——
-
可視化GC窗口(space)
-
圖形統計窗口(Graphs)
-
幸存者年齡直方圖窗口(Histogram)
下面分別介紹各自窗口與其顯示的數字表示——
-
可視化GC窗口(space)
VisualGC窗口是最左的窗口,分成三條垂直柱體,在JDK1.8版本中,分別代表metaSpace元空間、Old老年代、新生代,其中新生代又划分成 Eden 區, S0 區, S1區三部分。柱體里顏色部分代表占用的空間,空白部分表示剩余空間。監控項目的堆進程時,這些代表顏色的地方都是動態變化的。
-
圖形統計窗口(Graphs)
圖形窗口顯示各種統計值隨時間的變化。
一、Compile Time
顯示將Java字節代碼編譯為本機代碼所花費的時間量。窄脈沖表示持續時間相對較短,寬脈沖表示持續時間較長。
-
編譯任務的數量5508;
-
累計編譯時間27.721s。
二、Class Loader Time
此面板顯示在類加載和卸載活動中花費的時間量。窄脈沖表示持續時間相對較短,寬脈沖表示持續時間較長。
-
加載的類數量:11337;
-
卸載的類的數量:0
-
累計的類加載時間:15.589s
三、GC Time
此面板顯示垃圾收集活動所花費的時間量。窄脈沖表示持續時間相對較短,寬脈沖表示持續時間較長。
-
執行GC垃圾回收總次數:9次(9 collections代表自監視以來執行9次GC,其中,包括新生代的Minor GC和老年代的Full Gc)
-
累計的GC時間:888.929ms;
-
若JVM維護hotspot.gc.cause和hotspot.gc.last_cause計數器,則gc事件的原因將出現在last Cause中;
四、Eden Space
此面板顯示Eden空間隨時間的利用情況。它是年輕代的三個空間之一,另外兩個分別是S0、S1。空間的當前容量可以根據收集器策略動態更改,即通過修改--Xmn參數,會改變其大小。
標題欄第一個參數代表最大容量,第二個參數代表當前容量,后跟當前占用空間。此外,還包含了年輕代GC事件數量和GC累計時間。
-
Eden Space最大可分配空間:102.500M;
-
Eden Space當前已分配空間:102.500M;
-
Eden Space當前占用空間:54.523M(當積累的占用空間超過102.500M,就會在Eden Space發生一次Minor GC)
-
Minor GC次數:6次
-
Minor GC花費時間:286.621ms
五、Survivor 0 and Survivor 1
HotSpot JVM把年輕代分為了三部分:1個Eden區和2個Survivor區(分別叫from和to),默認大小比例為Eden:Survivor0:Survivor1=8:1:1的。 新創建的非大對象,會存放在Eden區和一個作為from的Survivor區,當發生一次Minor GC時,就會將Eden區和作為from的Survivor區內仍存活的對象,復制到另一個作為to的Survivor區,然后清理掉原來Eden區和作為from的Survivor區內對象。因此,S0 和 S1 之間至少有一個肯定是空閑的。
-
Survivor 0區最大分配容量:12.750M;
-
Survivor 0區當前已分配容量:12.750M;
-
Survivor 0區當前占用容量:0M;
五、Old Gen
面板顯示老年代隨着時間推移的利用情況。
-
Old Gen 最大分配空間128M;
-
Old Gen 已分配空間128M;
-
Old Gen 當前占用空間38.06M;
-
Old Gen 發生的GC次數:3次;
-
Old Gen 發生的GC花費時間:602.309ms;
六、Perm Gen
標題欄在括號中顯示空間的名稱及其最大容量和當前容量,后跟空間的當前占用大小。
visual VM工具的相關功能使用主要就介紹那么多,下面就介紹一下入門調優的案例,小白都能看懂的。
假如某天你觀察到使用visual VM工具的Visual GC插件觀察到以下的圖表——新生代Eden區已經發生了8168次Minor GC,耗時39.754s,另外老年代也發生了24次GC,耗時5.124s。可見,該JVM參數設置得極不合理,導致已經過於頻繁發生Minor GC。
-
那么,我們該如何調優進行設置呢?
JVM調優無外乎就是對相關參數進行設置,這里,我們先做一些最簡單的參數,好讓小白也能理解,那么,就暫時先對-Xms、-Xmx、-Xmn參數設置。
截圖中,可以看到新生代中的Eden區頻繁發生Minor GC,原因之一是分配的空間過小,目前是204.875M,導致當前占用空間經常超過204.875M,進而發生GC。若要分析是哪些代碼頻繁創建對象,還得進一步通過dump等方式進行分析,這里暫時不展開。
解決該Eden區其中一個思路是,提升分配給Eden區的大小。
那么,多大才比較合適呢?
這時,Visual VM的監視欄中的堆監控就派上用場了。可以觀察到藍色模塊高度比較均衡地對應在縱坐標240MB的樣子,也就是說,新創建的對象其占有的大小達到近300MB,而Eden Space+其中一個Survivor才230MB,可見,每次新創建的對象很容易就超過新生代,這就意味着,頻繁發生Minor GC是必然的,從圖的橫坐標可以看出,每30ms內,就發生了2到3次的Minor GC。
為了避免Eden區頻繁發生Minor GC,根據堆監控圖表,可以考慮在設置JVM參數時,適當提升分配給Eden的空間,至少需要在240MB以上,可以考慮先設置到300MB的樣子,看下效果,當然,這是在項目比較平穩運行的情況下來看的,實際生產當中,還需要考慮到高峰時期。
就暫且先設置Eden區為320MB,考慮到Eden:Survivor0:Survivor1=8:1:1比例,也就是8:2,若要分配Eden=320M,那么,可以根據8/2=320/x算出來,x=80,這里的x就是兩個Survivor總大小,即每個Survivor分配40MB,那么,年輕代總共需要分配的大小為(320M+80M)=400M,即-Xmn400m
再來看下老年代,目前老年代發生了24次GC,最大分配空間是256MB,當前最小分配空間是71.48M,可見,還可以適當進行優化。
一般而言,最大分配空間與最小分配空間最好保持一致,這樣避免每次空間不夠時都需自動提升當前分配大小。
可以暫且考慮最大分配空間與當前分配空間都保持在256M,而根據堆=新生代+老年代,不包括永久代(方法區)。在新生代已經分配300MB情況下,若要讓老年代最大與最小分配空間都為256MB,那么,就需要對JVM堆分配400M+256M=656M的空間大小,即設置-Xms656M、-Xmx656M;
元空間暫且可以不考慮進行分配。
根據以上得出的參數,進行設置,然后以設置好的參數進行項目重啟,根據新一輪圖表展示,繼續進行參數優化,循環調試,直到新生代和老年代的GC頻率都保持一個比較平衡的水准。
以上,就是主要介紹了JVM監控與調優工具,同時,簡單說明了一下如何進行參數調優,實際上,還需調試更多JVM相關參數,才能達到優化效果,至於其他的JVM參數調試,本文暫且不展開介紹了。
最后,需要注意一點,本地環境使用jstatd模式遠程連接線上服務端的JVM時,是不能在本地獲取到堆棧信息的,可以手動生成dump文件來分析出現異常的堆棧信息。
一、設置參數在異常發生時自動生成dump文件。
-
-XX:+HeapDumpOnOutOfMemoryError 表示當JVM發生OOM時,自動生成DUMP文件。
-
-XX:HeapDumpPath=存儲文件/目錄 表示生成DUMP文件的路徑
二、手動生成dump分析文件
執行jmap -dump:format=b,file=20210321.dump 7132,其中7132是對應項目的進程PID。
將獲取到的dump文件手動導入到Visual VM工具,就可以分析哪些對象占用內存高了,往往可以分析出哪些對象造成了內存泄露問題。