JVM 除錯工具入門

2016-11-29    分類:JAVA開發、程式設計開發、首頁精華1人評論發表於2016-11-29

本文由碼農網 – 聶嘯原創,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃

筆者上週末連續兩天凌晨都收到了系統的記憶體使用率過高報警,在分析監控系統記錄的記憶體使用率曲線和記憶體使用情況後發現,主要是因為在老年代遲遲沒有觸發full gc導致監控系統連續多次監測到可用記憶體過低,而觸發的報警。在系統觸發一次full gc之後,記憶體使用率會顯著下降,報警也沒有持續下去。由於無法復現問題,具體原因仍未找到,但是通過此過程,學習到的記憶體分析工具與方法,卻值得記錄一番。

jstat

jstat是HotSpot Java虛擬機器的效能統計工具。其基本的語法描述如下:

jstat [generalOption | outputOptions | vmid [interval[s|ms] [count]]]

generalOption

generalOption是針對jstat功能的描述,包括兩個引數 -help  與 -options ,分別用於提示jstat的用法和支援的統計選項。該選項具有排他性,只能單獨使用。

outputOptions

outputOptions包括兩類引數:狀態統計 和 格式化輸出。狀態統計引數用於指定jstat命令希望獲取虛擬機器哪方面的資訊,而格式化引數則用於控制命令輸出的展示樣式。

jstat支援的狀態統計引數(即jstat -options的輸出)及功能描述如下:

-class              統計類載入行為

-complier           統計HotSpot即時編譯器的行為

-gc                 統計關於堆記憶體垃圾回收的行為

-gccapacity         統計堆記憶體中各分割槽的使用情況

-gccause            垃圾回收行為彙總,比-gcutil多輸出最近兩次垃圾回收的原因

-gcnew,-gcold       新生代,老年代行為資訊(記憶體量,閾值,垃圾回收次數等)

-gcnewcapacity      新生代記憶體容量和使用量資訊

-gcoldcapacity      老年代記憶體容量和使用量資訊

-gcpermcapacity     持久區記憶體容量和使用量資訊

-gcutil             垃圾回收行為彙總

-printcompilation   HotSpot編譯方法統計

格式化引數包括三個:-h n ,-t 和 -JjavaOption. -h引數指定每隔n行重新顯示一次列名;-t引數控制在輸出第一列新增時間戳資訊;-JjavaOption用於傳遞javaOption到java程式啟動引數。比如,-J-Xms48m 設定java啟動最小記憶體為48M。

vmid

vmid是待監測的目標java程式識別符號,可用 jps 和Linux系統下的 ps 等操作獲取。vmid引數也支援以URI形式指定的遠端主機上執行的java程式,不常用,不再贅述。

interval and count

這兩個引數用於控制jstat命令監測並輸出的頻率,interval預設引數為毫秒,如果設定了該引數,jstat命令將每隔interval的時間輸出一次,count控制jstat命令輸出樣例的個數,也就是輸出的行數。如果不設定,預設為無限,jstat會一直進行輸出直到目標程式退出或者jstat命令終止。

樣例

使用 -gcutil 引數檢視程式16058發生的垃圾回收行為,每2秒列印一次結果,一共列印5次。命令輸出的每一列都使用簡稱的形式展示,下圖中 S0 表示Survivor 0區的空間使用比例, E, O, P 分別代表Eden, Old和Perm空間使用率,YGC 表示young gc的次數,YGCT 表示young gc消耗的時間。GCT 則用來統計執行gc的總時間。

使用 -gc 引數檢視更詳細的垃圾回收與堆記憶體資訊。 S0 以及 E 等各記憶體區域標識字尾中 C 表示 Capacity, U 表示Utilization,引數對應的數值顯示的是真實容量,而不是百分比。

使用 -gcnew 引數檢視新生代的記憶體和垃圾回收情況,由於命令附帶了引數 t ,所以第一列列印了時間戳的資訊。 TT 參數列示物件在gc時被放入老年代的年齡期限閾值,MTT 參數列示最大閾值. 這兩個引數之間 MTT 設定了 TT 可取的最大值,TT 實際控制著物件進入老年代的年齡限制,會隨著垃圾回收過程而發生變化。

當年齡從1開始的物件大小累計超過了Survivor區域的1/2(TargetSurvivorRatio所定義)時,會計算一個Thenuring Threshold,超過這個年齡的新生代物件會進入到老年代,即使這時候新生代還有很多的空間。注意MaxTenuringThreshold只是設定了最大的Thenuring Threshold,不是說只有大於Max Tenuring Threshold才會進入到老年代,而是隻要超過了計算出來的Tenuring Threshold就會進入老年代,MaxTenuringThreshold規定了Tenuring Threshold的最大值而已。Tenuring Threshold這個值在每一輪GC後都會動態計算,它與TargetSurvivorRatio以及Survivor區的大小有關係,TargetSurivivor預設是50即Survivor的1/2, 會計算出一個Desired Survivor Size,當年齡從1開始的物件大小累計超過了這個Desired Survivor Size,那麼這個age就是Tenuring Threshold的值

使用 -gcnewcapacity 引數檢視新生代的記憶體佔用情況。NGC : new generation capacity, 新生代記憶體大小。NGCMN 表示新生代分配記憶體的最小值,NGCMX 新生代分配記憶體的最大值,NGCMX=S0CMX+S1CMX+ECMX.

jstack

jstack用於列印指定java程式或者核心檔案中所有java執行緒當前時刻正在執行的方法堆疊追蹤情況,也就是執行緒的snapshot。生成執行緒的快照主要用於定位執行緒長時間出現停頓的原因:如死鎖,等待外部資源等。jstack的命令格式如下:

jstack [option] pid/executable core/[server-id@]remote-hostname-or-IP

- pid

待追蹤的java程式ID,可使用jps等方式獲得

- executable

產生core dump的java可執行程式(jar檔案)

- core

列印棧追蹤資訊的核心檔案

options

-F      當正常輸出的請求(jstack [-l] pid)不被響應時,強制執行stack dump

-l      除了堆疊外,列印關於鎖的附加資訊

-m      如果呼叫本地方法,同時列印java和本地C/C++棧幀

-h      列印幫助

jstack在分析死鎖,阻塞等效能問題上非常有用,根據列印的堆疊資訊可以定位到出問題的程式碼段。定位問題的思路根據要解決的問題而發生不同,比如可以首先找到java程式中最耗cpu的執行緒,再根據執行緒id在jstack的輸出中定位,或者使用指定的執行緒名稱定位。下面看一下jstack的輸出格式:

main 執行緒名稱

prio=5 執行緒優先順序為5

tid=7f8a7c001800 jvm中執行緒識別符號

nid=0x700000a19000 16進製表示的本地執行緒識別符號

runnnable 執行緒狀態

[70000a17000] 執行緒的起始地址

從第二行起為函式呼叫棧

上述資訊中最重要的狀態莫過於執行緒的狀態,當程式發生問題時往往能夠據此幫我們找到問題的所在。在jstack的輸出中,執行緒所處的狀態包括:

- Runnable: 正在執行

- Wait on condition: 該狀態出現線上程等待某個條件的發生。具體是什麼原因,可以結合 stacktrace來分析。最常見的情況是執行緒在等待網路的讀寫。如果網路資料沒準備好,執行緒就等待在那裡。另外一種出現 Wait on condition的常見情況是該執行緒在 sleep,等待 sleep的時間到了時候,將被喚醒。

- Wait for monitor entry: Java通過物件監視器來進行執行緒的互斥與同步的,每個物件都有一個物件監視器,在對物件加鎖的過程中,任何時刻只有一個執行緒擁有這個物件監視器,其他請求獲得此資源的執行緒將會被分為兩種:線上程獲取到物件監視器,但等待的資源仍未到達時,執行緒可能呼叫Object.wait(),釋放鎖並進入Object.wait()的狀態,重新等待;而那些從未獲得過此物件監視器的執行緒就將被標識為Wait for monitor entry的狀態。

- Object.wait: 等待物件監視器(鎖)的狀態。

關於兩種等待狀態可以參考下面的圖(來源於網路):

jmap

jmap用於生成java程式的heapdump或者堆記憶體的詳細資訊。可以用來分析java程式堆記憶體被各種例項佔據的比例或者GC回收了哪些物件等資訊。jmap的命令格式與jstack一致,不再贅述。

options

- <no option>

列印每一個共享物件的起始地址,範圍等資訊

-dump:\[live,]format=b,file=<filename>

以二進位制形式列印java堆的dump資訊到指定檔案中,指定live引數,只列印存活的物件

-heap

顯示java堆的詳細資訊:GC演算法,堆配置以及分代情況

-histo\[:live]

顯示堆中每一個java類例項的個數,佔據空間的大小,類名全稱等。live引數控制輸出存活物件。

-premstat

以classLoader為統計入口,顯示永久代的生存狀態。每個載入器的名稱,存活狀態,地址,父載入器和已經載入類的數量等資訊將被列印。

-F

在正常的命令對-dump或者-histo沒有響應時,強制執行,生成dump資訊

-J<flag>

map啟動時傳遞給jvm的引數,比如在64位機器上就要使用jmap -J-d64 -heap pid來執行命令。

參考連結

1.http://docs.oracle.com/javase/7/docs/technotes/tools/share/jstat.html

2.http://docs.oracle.com/javase/7/docs/technotes/tools/share/jstack.html

3.http://docs.oracle.com/javase/7/docs/technotes/tools/share/jmap.html

4.https://my.oschina.net/feichexia/blog/196575

5.http://go-on.iteye.com/blog/1673894

6.http://blog.csdn.net/iter_zc/article/details/41802365

7.《深入理解JVM虛擬機器》周志明著

本文連結:http://www.codeceo.com/article/jvm-debug-tools-guide.html
本文作者:碼農網 – 聶嘯
原創作品,轉載必須在正文中標註並保留原文連結和作者等資訊。]

相關文章