GC root & 使用MAT分析java堆


當我們的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 .hprof 的head dump文件。如果我們希望將head dump生成在其他目錄或者文件,可以使用如下參數:

java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=

當然還有其他生成head dump文件的方式,具體可以參考java-heap-dump-capture

2 GC root

2.1 概念

Garbage Collection Roots.

深入理解java虛擬機中提到,可作為GC Roots的對象包括下面幾種:

  1. 虛擬機棧(棧幀中的本地變量表)中引用的對象
  2. 方法區中類靜態屬性引用的對象
  3. 方法去中常量引用的對象
  4. 本地方法棧JNI引用的對象

怎么理解呢?

  1. 首先對於上述的第一點應該注意是一個虛擬機棧而不是方法棧,每個java線程有一個虛擬機棧
  2. 其次為什么這些可以作為GC root,其實比較好理解,因為棧中的對象肯定是正在使用的,所以可以從這些對象開始遍歷,然后得出所有還在引用的對象;
  3. 最后,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. 這里具體表示這個是當前線程棧中的 變量,類型是上一行的Stu類型。所以這里GC root就是引用了一個Stu對象的棧中的引用,你也可以理解為這個 就是個Stu的一個對象。

注意:一個對象可以有多個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,來確定對象沒有被釋放的原因,有兩個選項

  1. 右擊 -> path to gc roots -> exclude all phantom/weak/soft etc. references
  2. 右擊 -> 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

一般通過分析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 參考

  1. https://www.baeldung.com/java-heap-dump-capture
  2. https://help.eclipse.org/luna/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Fconcepts%2Fgcroots.html&cp=37_2_3
  3. https://stackoverflow.com/questions/26232733/thread-as-a-gc-root
  4. https://www.zhihu.com/question/47258557
  5. https://blog.csdn.net/jji8877032/article/details/84503063
  6. http://www.lightskystreet.com/2015/09/01/mat_usage/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM