GC root & 使用MAT分析java堆

當咱們的java程序遇到頻繁full gc或者oom的時候,咱們經常須要將當前的heap dump出來進行進一步的分析。MAT是用於分析heap dump的神器。html

1 生成heap dump

heap dump是jvm內存中某一時刻全部對象的的快照。一般用於定位java程序的內存泄露或者優化內存。一般能夠經過如下幾種方式生稱dump文件:java

1.1 jmap正則表達式

jmap -dump:[live,]format=b,file= dom

live是可選項,若是加上了live,那麼只會dump存活的對象,不會dump將被gc的對象。 jmap的使用舉例來講,假如經過jps獲得進程id爲19234:eclipse

jmap -dump:format=b,file=heap.hprof 19234jvm

注意: jmap是實驗性質的,而且不會長久支持的(This command is experimental and unsupported)jsp

1.2 jcmd
jcmd的功能很是多,用來向jvm發送請求。使用jcmd命令必須是在和jvm進程同一個機器上運行。使用jcmd生成head dump的命令是:字體

jcmd GC.heap_dump [-all] 優化

從試驗來看,這裏的file-path需要是絕對路徑,不能是相對路徑。 all是可選項,不寫all就相似jmap寫上了live。使用舉例如:ui

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/
相關文章
相關標籤/搜索