MAT工具定位分析Java堆記憶體洩漏問題方法

朱季謙發表於2021-06-24

一、MAT概述與安裝

MAT,全稱Memory Analysis Tools,是一款分析Java堆記憶體的工具,可以快速定位到堆內洩漏問題。該工具提供了兩種使用方式,一種是外掛版,可以安裝到Eclipse使用,另一種是獨立版,可以直接解壓使用。

我把獨立版MAT安裝包放到了網盤上,方便直接下載————
連結:https://pan.baidu.com/s/1CG887mHBcnVq3RxOzmxRvA
提取碼:rhb5

獨立版解壓後,其內部檔案是這樣的——

image

這裡有一個MemoryAnalyzer.ini檔案,裡面有一個Xmx引數,預設是-Xmx1024m,這代表MAT的最大記憶體大小,根據具體分析的dump檔案大小來做適當調整。

點選MemoryAnalyzer.exe,啟動完成後,即可以使用它來檢查定位記憶體洩漏相關的問題了。

image


二、記憶體洩漏案例分析

下面,我會結合一個小案例來分享MAT的使用。

首先,用IDEA建立一個測試類——

public class example {
    public static void main(String[] args)  {
        List<User> list=new ArrayList<>();
        while (true){
            list.add(new User());
        }
    }
}

class User {
    private String name="demo";
    public User() {
    }
}

給這個測試類設定虛擬機器引數,設定如:-Xms2m -Xmx2m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/local_system/git/demo/heapdump.hprof

這幾個引數的意義是:

-Xms2m -Xmx2m:堆最小記憶體為2M,最大記憶體為2M。這裡沒有顯示設定新生代大小,它會自動分配新生代大小,分配完剩下的,就是老年代大小了。

-XX:+HeapDumpOnOutOfMemoryError:指發生記憶體溢位的時候,會自動生成一個二進位制的堆快照檔案,這個快照檔案以.hprof字尾結尾。用MAT分析堆記憶體資訊,就是利用這個.hprof檔案。除了可以設定相應的虛擬機器引數外,還可以通過jmap指令來獲取到某個程式的堆快照檔案,執行指令格式是:

jmap -dump:format=b,file=<dumpfile.hprof> <pid>

例如:jmap -dump:format=b,file=20210618.dump 7132,那麼,這裡20210618.dump就是自定義的dump堆轉儲檔名字,而7132是程式ID。只是使用jmap指令可能有一點不好的地方是,記憶體溢位是某個時間點發生的事情,jmap指令去獲取到dump檔案,存在時間差問題。而HeapDumpOnOutOfMemoryError則是在發生記憶體溢位時,同時生成的,故而會更準確些。

-XX:HeapDumpPath=D:/local_system/git/demo/heapdump.hprof:記憶體溢位產生的堆快照自動儲存路徑,可以自定義指定路徑。

其實,在實際生產環境裡,除了這些基本引數外,還有其他的JVM引數,這些引數都是用來調優的重點所在。

這裡暫且以這些引數做實驗,在執行IDEA時,可以將這些引數設定在IDEA的“Run/Debug Configurations”彈出框的VM options輸入框裡,如下截圖所示——

image

按照以上方式設定好後,就可以執行該案例程式碼了,執行一會兒後,就會出現以下提示——

image

這表明,該程式碼已經發生記憶體溢位了,即ArrayList儲存的物件大小已經超過堆記憶體,導致無法進行垃圾回收,也就是出現記憶體洩漏,進而導致記憶體溢位。當然,在本地是可以看到這麼簡單的異常提示的,但是線上上伺服器上,就沒有那麼明顯的記憶體溢位提示,就需要獲取到產生的堆快照dump檔案,然後再進一步分析堆快照資訊。


三、使用MAT分析堆轉儲dump檔案

我們將這個heapdump.hprof檔案匯入到MAT裡。啟動MAT,點選File,選擇Open Heap Dump,然後選擇對應的hprof檔案。 ![image](https://img2020.cnblogs.com/blog/1545382/202106/1545382-20210624185815985-572998625.png)

在彈出框處,選擇Leak Suspects Report,這是指記憶體洩漏報告——

image

點選Finish後,展示Overview主頁面如下——

image

Overview主頁面顯示應用程式記憶體使用情況的概覽,中間的餅圖按retained size來顯示最大的物件。注意一點是,在MAT中,會有兩種大小表示,一個是Retained size,還有一個是Shallow Size,那麼,兩者有什麼區別呢?

  • Shallow Size:表示物件自身佔用的記憶體大小,不包括它引用的物件。
  • Retained size:當前物件記憶體大小+當前物件直接或間接引用的物件大小,全部的總和,簡單理解,就是當前物件被GC後,總共能釋放的記憶體大小。

1.Details顯示的是dump檔案的情況,表示堆大小為1.1MB,有516個class,40.2k個Object,3個類載入器等;

2.功能檢視模組;

3.報表模組;

我比較喜歡用Actions的Histogram檢視和Reports的Leak Suspects報表,Histogram檢視是以類為維度來顯示其例項數和每個類的使用記憶體量,可以協助我們查詢哪些類物件佔用較大記憶體;Leak Suspects則可以協助分析記憶體洩漏的原因所在。

- Histogram檢視

image

以Class Name為維度,分別展示各個類的物件數量,Shallow Size,Retained size。這裡有一個疑惑是,Shallow Size和Retained size沒有顯示是以什麼為單位的,它預設是以byte為單位的,若要顯示地讓單位展示出來,可以這樣設定,點選Window->Preferences

image

選擇最後一項,點選Apply and Close——

image

再重新開啟Histogram檢視,就會生效了,單位就顯示出來了——

image

根據這個Histogram檢視,我們可以發現,com.example.demo.User數量和佔用記憶體大小都比較高,同時說明了該User物件一直沒有被GC回收掉,這時,可以右擊,彈出框有以下一些選單選項——
image

  • List objects

    使用List Object可以檢視物件引用關係,這裡檢視引用功能,包括本物件引用外部物件with outgoing references與外部物件引用本物件with incoming references。

    1. with outgoing references

      使用該功能,可以檢視物件內部都引用了哪些外部物件,例如,這裡的User,其引用外部物件情況如下:

      image

      對照這個案例的程式碼,可見,在建立這個User物件時,內部屬性name就會指向一個字串地址,換言之,該User物件內部有個引用指向了一個name字串地址。

      image

    2. with incoming references

    ​ 使用該功能,可以檢視該物件都被哪些外部所引用了——

    image

​ 在案例程式碼當中,是以list.add(User)來不斷儲存User物件的,如截圖所示,通過MAT可確定,存在一個ArrayList集合一直引用該User物件。

在實際開發當中,一個物件可能引用了諸多其他外部物件或者被諸多外部物件所引用,若一直引用著,說明某個物件一直存在GC ROOT可達的情況,反過來就意味著,該被引用的物件一直無法被GC回收處理,那麼就可能會一直存在堆記憶體裡,進而造成記憶體洩漏的情況。

  • Merge Shortest Paths to GC Roots->exclude all phantom/weak/soft etc. references

image

排除其他引用,只觀察GC路徑上強引用的物件,所觀察到的,都是仍存活的物件。

除此之外,Histogram檢視仍有其他功能,後期在學習過程當中,不斷進行完善。

- Leak Suspects報表

image

Leak Suspects報表很直觀地展現了一個餅圖,圖中顏色深的部分表示可能存在記憶體洩漏的嫌疑。每一個模組都有對應的詳情資訊。

這裡拿模組a來講解,其詳情部分有一句話很關鍵:The memory is accumulated in one instance of "java.lang.Object[]", loaded by "", which occupies 617.55 KB (52.54%) bytes.The stacktrace of this Thread is available. See stacktrace. See stacktrace with involved local variables.

這句話翻譯過來就是,記憶體累積在一個“java.lang.Object[]”例項中,由“”載入,佔用617.55 KB(52.54%)位元組。此執行緒的堆疊跟蹤可用。請參見stacktrace。請參閱包含區域性變數的stacktrace。

點選stacktrace,進入到一個頁面,可以看到日誌資訊——

image

在這裡,從下往上看異常資訊,可以快速定位記憶體洩漏地方出現在哪個類方法裡的哪行程式碼。

我很喜歡使用這個功能,通過獲取線上堆轉儲檔案,便可以通過Leak Suspects定位到記憶體洩漏快速定位在哪一行程式碼。

相關文章