歡迎光臨
每天分享高質量文章

MAT入門到精通(一)

儘管JVM提供了自動記憶體管理的機制,試圖降低程式員的開發門檻,確實也實現了這一標的,在日常開發中,我們一般都不需要關心物件的記憶體釋放。JVM大部分都是使用trace演演算法來判斷一個物件是否該被回收,那麼JVM只能回收那些從gc roots不可達的物件。

如果我們在使用某些大的物件、集合物件或者一些三方包裡的資源,忘記及時釋放資源的話,還是會造成JVM的記憶體洩漏或記憶體浪費的問題。因此,如果想成為更高階的Java開發工程師,我們需要瞭解常見的問題排查的辦法和工具,這個系列的文章,準備介紹一個用來做JVM堆記憶體分析的工具——MAT(Memory Aanlysis Tool)。

MAT的官網在:https://www.eclipse.org/mat/,可以看下它的介紹——MAT是一款高效能、具備豐富功能的Java堆記憶體分析工具,可以用來排查記憶體洩漏和記憶體浪費的問題。

MAT的安裝和設定

01
 

1.1
在Mac上安裝MAT

MAT 支援兩種安裝方式,一種是”單機版“的,也就是說使用者不必安裝 Eclipse IDE 環境,MAT 作為一個獨立的 Eclipse RCP 應用執行;另一種是”整合版“的,也就是說 MAT 也可以作為 Eclipse IDE 的一部分,和現有的開發平臺整合。

這裡我們考慮獨立安裝,在觀望的下載頁面,選擇mac os版本的安裝檔案下載即可。

1.2
安裝中遇到的坑
  1. 啟動直接報錯,系統預設的workspace是隻讀的,更換掉即可。怎麼更換呢,在檔案/Applications/mat.app/Contents/Eclipse/MemoryAnalyzer.ini中進行修改。

  2. 啟動後,UI介面沒反應,參考:https://www.eclipse.org/forums/index.php/t/1090889/,換個包即可。這個問題我遇到過很多次。

1.3
MAT的設定

配置mat的堆記憶體大小

我的電腦是8C16G的,那理論上分析10G的堆檔案沒問題,但是MAT預設的配置沒有這麼大,需要在/Applications/mat.app/Contents/Eclipse/MemoryAnalyzer.ini檔案中進行修改。如下圖所示,我將我的MAT自己的執行時堆記憶體配置成了6G。

配置MAT的使用

MAT的配置頁面可以從Window——>Preferences找到,如下圖所示。

MAT的一般配置有幾個選項

  1. Keep unreachable objects:如果勾選這個,則在分析的時候會包含dump檔案中的不可達物件;

  2. Hide the getting started wizard:隱藏分析完成後的首頁,控制是否要展示一個對話方塊,用來展示記憶體洩漏分析、消耗最多記憶體的物件排序。

  3. Hide popup query help:隱藏彈出查詢幫助,除非使用者透過F1或Help按鈕查詢幫助。

  4. Hide Welcome screen on launch:隱藏啟動時候的歡迎介面

  5. Bytes Display:設定分析結果中記憶體大小的展示單位

可以看出,MAT不僅支援HPROF檔案的分析,還支援DTFJ檔案的分析。一般sun公司系列的JVM生成的dump檔案都是HPROF格式的,IBM的JVM生成的dump檔案時DTFJ格式的。

 

基本概念

02
 

2.1
Heap Dump

Heap Dump是Java行程在某個時刻的記憶體快照,不同JVM的實現的Heap Dump的檔案格式可能不同,進而儲存的資料也可能不同,但是一般來說。

Heap Dump中主要包含當生成快照時堆中的java物件和類的資訊,主要分為如下幾類:

  • 物件資訊:類名、屬性、基礎型別和取用型別

  • 類資訊:類載入器、類名稱、超類、靜態屬性

  • gc roots:JVM中的一個定義,進行垃圾收集時,要遍歷可達物件的起點節點的集合

  • 執行緒棧和區域性變數:快照生成時候的執行緒呼叫棧,和每個棧上的區域性變數

Heap Dump中沒有包含物件的分配資訊,因此它不能用來分析這種問題:一個物件什麼時候被建立、一個物件時被誰建立的。

2.2
Shallow vs. Retained Heap

Shallow heap是一個物件本身佔用的堆記憶體大小。一個物件中,每個取用佔用8或64位,Integer佔用4位元組,Long佔用8位元組等等。

Retained set,對於某個物件X來說,它的Retained set指的是——如果X被垃圾收集器回收了,那麼這個集合中的物件都會被回收,同理,如果X沒有被垃圾收集器回收,那麼這個集合中的物件都不會被回收。

Retained heap,物件X的Retained heap指的時候它的Retained set中的所有物件的Shallow si的和,換句話說,Retained heap指的是物件X的保留記憶體大小,即由於它的存活導致多大的記憶體也沒有被回收。

leading set,物件X可能不止有一個,這些物件統一構成了leading set。如果leading set中的物件都不可達,那麼這個leading set對應的retained set中的物件就會被回收。一般有以下幾種情況:

  1. 某個類的所有實體物件,這個類物件就是leading object

  2. 某個類記載器載入的所有類,以及這些類的實體物件,這個類載入器物件就是leading object

  3. 一組物件,要達到其他物件的必經路徑上的物件,就是leading object

在下麵這張圖中,A和B是gc roots中的節點(方法引數、區域性變數,或者呼叫了wait()、notify()或synchronized()的物件)等等。可以看出,E的存在,會導致G無法被回收,因此E的Retained set是E和G;C的存在,會導致E、D、F、G、H都無法被回收,因此C的Retined set是C、E、D、F、G、H;A和B的存在,會導致C、E、D、F、G、H都無法被回收,因此A和B的Retained set是A、B、C、E、D、F、G、H。

2.3
Dominator Tree

MAT根據堆上的物件取用關係構建了支配樹(Dominator Tree),透過支配樹可以很方便得識別出哪些物件佔用了大量的記憶體,並可以看到它們之間的依賴關係。

如果在物件圖中,從gc root或者x上游的一個節點開始遍歷,x是y的必經節點,那麼就可以說x支配了y(dominate)。

如果在物件圖中,x支配的所有物件中,y的距離最近,那麼就可以說x直接支配(immediate dominate)y。

支配樹是基於物件的取用關係圖建立的,在支配樹中每個節點都是它的子節點的直接支配節點。基於支配樹可以很清楚得看到物件之間的依賴關係。

現在看個例子,在下麵這張圖中

  1. x節點的子樹就是所有被x支配的節點集合,也正式x的retained set;

  2. 如果x是y的直接支配節點,那麼x的支配節點也可以支配y

  3. 支配樹中的邊跟物件取用圖中的取用關係並不是一一對應的。

2.4
Garbage Collection Roots

在MAT中,gc roots的概念跟研究垃圾收集演演算法時候的概念稍微有點不同。gc roots中的物件,是指那些可以從堆外訪問到的物件的集合。如果一個物件符合下麵這些場景中的一個,就可以被認為是gc roots中的節點:

  1. System Class:由bootstrap classloader載入的類,例如rt.jar,裡面的類的包名都是java.util.*開頭的。

  2. JNI Local:native程式碼中的區域性變數,例如使用者編寫的JNI程式碼或JVM內部程式碼。

  3. JNI Global:native程式碼中的全域性變數,例如使用者編寫的JNI程式碼或JVM內部程式碼。

  4. Thread Block:被當前活躍的執行緒鎖取用的物件。

  5. Thread:正在存活的執行緒

  6. Busy Monitor:呼叫了wait()、notify()或synchronized關鍵字修飾的程式碼——例如synchronized(object)synchronized方法。

  7. Java Local:區域性變數。例如函式的輸入引數、正在執行的執行緒棧裡建立的物件。

  8. Native Stack:native程式碼的輸入或輸出引數,例如使用者定義的JNI程式碼或JVM的內部程式碼。在檔案/網路IO方法或反射方法的引數。

  9. Finalizable:在finalize佇列中等待它的finalizer物件執行的物件。

  10. Unfinalized:多載了finalize方法,但是還沒有進入finalize佇列中的物件。

  11. Unreachable:從任何gc roots節點都不可達的物件,在MAT中將這些物件視為root節點,如果不這麼做,就不能對這些物件進行分析。

  12. Java Stack Frame:Java棧幀,用於存放區域性變數。只在dump檔案被解析的時候會將java stack frame視為物件。

  13. Unknown:沒有root型別的物件。有些dump檔案(例如IBM的Portable Heap Dump)沒有root資訊。

     

獲取Dump檔案

03
 

  1. 透過MAT生成dump檔案 透過這個路徑找到生成dump檔案的對話方塊 選擇一個行程,點選finish即可

  2. 透過jmap命令生成dump檔案

  • 命令格式:jmap -dump:live,format=b,file=heap.bin

  • 註意:如果要保留heapdump中的不可達物件,則需要把”:live“去掉,即使用命令”jmap -dump,format=b,file=heap.bin

  • 透過設定JVM引數自動生成 使用-XX:+HeapDumpOnOutOfMemoryError這個JVM引數,在Java行程執行過程中發生OOM的時候就會生成一個heapdump檔案,並寫入到指定目錄,一般用-XX:HeapDumpPath=${HOME}/logs/test來設定。

 

本號專註於後端技術、JVM問題排查和最佳化、Java面試題、個人成長和自我管理等主題,為讀者提供一線開發者的工作和成長經驗,期待你能在這裡有所收穫。

 

往期精彩回顧
JVM、GC和常用命令

ThreadLocal:Java中的影分身

原始碼分析:Java中的Thread的建立和執行

 

    閱讀原文

    贊(0)

    分享創造快樂