當我們的java程序遇到頻繁full gc或者oom的時候,我們常常需要將當前的heap dump出來進行進一步的分析。MAT是用於分析heap dump的神器。
1 生成heap dump
heap dump是jvm內存中某一時刻所有對象的的快照。通常用於定位java程序的內存泄露或者優化內存。通常可以通過以下幾種方式生稱dump文件:
1.1 jmap
jmap -dump:[live,]format=b,file=
live是可選項,如果加上了live,那么只會dump存活的對象,不會dump將被gc的對象。 jmap的使用舉例來說,假如通過jps得到進程id為19234:
jmap -dump:format=b,file=heap.hprof 19234
注意: jmap是實驗性質的,並且不會長久支持的(This command is experimental and unsupported)
1.2 jcmd
jcmd的功能非常多,用來向jvm發送請求。使用jcmd命令必須是在和jvm進程同一個機器上運行。使用jcmd生成head dump的命令是:
jcmd
GC.heap_dump [-all]
從試驗來看,這里的file-path須要是絕對路徑,不能是相對路徑。 all是可選項,不寫all就類似jmap寫上了live。使用舉例如:
jcmd 19234 GC.heap_dump -all /tmp/dump.hprof
1.3 自動捕獲head dump文件
可以通過加入jvm參數,當程序出現oom的時候,自動產生heap dump文件
java -XX:+HeapDumpOnOutOfMemoryError
該參數默認情況下會在我們啟動java進程的目錄下,產生一個名字叫 java_pid
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=
當然還有其他生成head dump文件的方式,具體可以參考java-heap-dump-capture
2 GC root
2.1 概念
深入理解java虛擬機中提到,可作為GC Roots的對象包括下面幾種:
- 虛擬機棧(棧幀中的本地變量表)中引用的對象
- 方法區中類靜態屬性引用的對象
- 方法去中常量引用的對象
- 本地方法棧JNI引用的對象
怎么理解呢?
- 首先對於上述的第一點應該注意是一個虛擬機棧而不是方法棧,每個java線程有一個虛擬機棧
- 其次為什么這些可以作為GC root,其實比較好理解,因為棧中的對象肯定是正在使用的,所以可以從這些對象開始遍歷,然后得出所有還在引用的對象;
- 最后,GC root到底是對象還是引用呢?可以理解為這里的引用就是對象,因為對於Java語言(非字節碼)來說單獨的引用(沒有指向對象的引用)沒有意義。也就是說我們寫了個引用但是沒有賦值,其實和沒寫是一樣的。可以通過2.2的舉例來加深下理解
2.2 舉例
我們可以用MAT來更詳細的理解:
public static void main(String[] args) throws Exception {
Stu stu = new Stu();
stu.teacher = new Teacher();
while (true) {
Thread.sleep(1000);
}
}
使用jcmd生成heap dump文件,用MAT打開后,搜索Teacher,然后我們看下這個類對應對象的“Path to gc root”:

小黃點表示這是個GC root. 這里具體表示這個是當前線程棧中的
注意:一個對象可以有多個GC root,同樣在MAT上看也就是多條“Path to gc root”
3 使用MAT分析heap dump
了解了如何生成heap dump和對gc root有了進一步的了解,我們可以用MAT來進一步分析heap dump
3.1 打開
MAT默認沒有顯示unreachable objects,在使用前我們先勾選上
Preferences -> Memory Analyzer
然后勾選上Keep unreachable objects

如果之前沒有勾選,后面要改的話,不會立刻生效,需要把解析的文件刪除掉,重新解析打開heap dump文件
然后打開文件
File -> Open Heap Dump
3.2 Overview
Overview 顯示了java堆的一些基本信息,比如大小、對象個數等,也包括一個對象所占內存比例的餅圖,有助於我們直觀上去查看占用內存比較大的對象
3.3 Histogram
Histogram即直方圖,是以類的粒度來顯示,可以使用正則表達式搜索感興趣的類
如圖中我們搜索Teacher,出現一個匹配項;Objects列為1,表示有一個Teacher的對象;Shallow Heap和Retained Heap的概念不在這里闡述了,簡單來說Shallow Heap就是對象本身的大小,Retained Heap是指當對象釋放后,引起其他對象釋放總共大小,Retained Heap和支配樹(dominator tree)概念有關系。一般情況下在分析的時候,我們會按照Retained Heap大小來排序,占用比較大的很有可能就是引起oom的對象。
前面說了Histogram是類粒度的,可以右擊來顯示該類的對象
“with incomming references”表示顯示對象和引用該對象的對象,如下圖。左邊的黑色字體表示變量名,而變量名的類型是它的上一行的左邊的類。
看到對象后,我們一般右擊來看下對象的GC root,來確定對象沒有被釋放的原因,有兩個選項
- 右擊 -> path to gc roots -> exclude all phantom/weak/soft etc. references
- 右擊 -> merge shortest paths to gc roots -> exclude all phantom/weak/soft etc. references
兩個的區別是1是顯示從該對象到gc roots的路徑,而且會顯示所有的gc roots(一個對象的gc root可以有多個); 2顯示的是從gc roots到對象的路徑,而且只顯示最短的一條路徑。 gc root的顯示在2.2中已經顯示過了
一般通過分析gc root的路徑和邏輯代碼,就可以很容易確定oom或者內存泄露的原因了
3.4 dominator tree
dominator tree即支配樹。支配樹的概念可以參考 支配樹。需要注意的是,支配樹並不等於path to gc roots。
Histogram是類粒度的,可以找到哪個類占用的堆內存比較多;dominator tree是對象粒度的,可以用來查看哪個對象引起占用堆內存比較大。
4 總結
一般來說對heap dump的分析是個比較綜合的過程,通過Histogram和dominator tree,通過gc roots和源碼綜合分析,可以得出最后的結論
5 參考
- https://www.baeldung.com/java-heap-dump-capture
- https://help.eclipse.org/luna/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Fconcepts%2Fgcroots.html&cp=37_2_3
- https://stackoverflow.com/questions/26232733/thread-as-a-gc-root
- https://www.zhihu.com/question/47258557
- https://blog.csdn.net/jji8877032/article/details/84503063
- http://www.lightskystreet.com/2015/09/01/mat_usage/