visualvm工具遠端對linux伺服器上的JVM虛擬機器進行監控與調優

朱季謙發表於2021-03-25

文/朱季謙

 

最近在做了一些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,啟動步驟如下——

  1. 找到服務端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

    設定成功——

  2. 在監控的遠端服務端啟動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)

下面分別介紹各自視窗與其顯示的數字表示——

  1. 視覺化GC視窗(space)

    VisualGC視窗是最左的視窗,分成三條垂直柱體,在JDK1.8版本中,分別代表metaSpace元空間、Old老年代、新生代,其中新生代又劃分成 Eden 區, S0 區, S1區三部分。柱體裡顏色部分代表佔用的空間,空白部分表示剩餘空間。監控專案的堆程式時,這些代表顏色的地方都是動態變化的。

  2. 圖形統計視窗(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工具,就可以分析哪些物件佔用記憶體高了,往往可以分析出哪些物件造成了記憶體洩露問題。



相關文章