JVM 內存分析工具 - MAT

MAT(Memory Analyzer Tools)是一個快速且功能豐富的 Java 堆分析器,可幫助您查找內存泄漏並減少內存消耗。使用 MAT 分析具有數億個對象的高效堆轉儲,快速計算對象的保留大小,查看誰阻止垃圾收集器收集對象,運行報告以自動提取泄漏嫌疑者。

1 簡介

MAT 是一款非常強大的內存分析工具,在 Eclipse 中有相應的插件,同時也有單獨的安裝包。在進行內存分析時,只要獲得了反映當前設備內存映像的 hprof 文件,通過 MAT 打開就可以直觀地看到當前的內存信息。

2 使用

2.1 準備 MAT

下載獨立版本的 MAT,下載地址:https://www.eclipse.org/mat/downloads.php,下載後解壓。找到 MemoryAnalyzer.ini 文件,該文件裏面有個 Xmx 參數,該參數表示最大內存佔用量,默認爲 1024m,根據堆轉儲文件大小修改該參數即可。

2.2 準備堆轉儲文件(Heap Dump)

堆轉儲文件(Heap Dump)是 Java 進程在某個時間內的快照(.hprof 格式)。它在觸發快照的時候保存了很多信息,如:Java 對象和類信息(通常在寫堆轉儲文件前會觸發一次 Full GC)。

堆轉儲文件信息:

  • 所有的對象信息,包括對象實例、成員變量、存儲於棧中的基本類型值和存儲於堆中的其他對象的引用值。
  • 所有的類信息,包括 classloader、類名稱、父類、靜態變量等。
  • GC Root 到所有的這些對象的引用路徑。
  • 線程信息,包括線程的調用棧及此線程的線程局部變量(TLS)。

多種方式獲取堆轉儲文件:

  • 通過 jmap 命令可以在 cmd 裏執行:jmap -dump:format=b,file=<文件名.hprof> <pid>
  • 如果想在發生內存溢出的時候自動 dump,需要添加下面 JVM 參數:-XX:+HeapDumpOnOutOfMemoryError
  • 使用 Ctrl+Break 組合鍵主動獲取獲取,需要添加下面 JVM 參數:-XX:+HeapDumpOnCtrlBreak
  • 使用 HPROF Agent 可以在程序執行結束時或受到 SIGOUT 信號時生成 Dump 文件,配置在虛擬機的參數如下:-agentlib:hprof=heap=dump,format=b
  • 使用 JConsole 獲取。
  • 使用 Memory Analyzer Tools 的 File -> Acquire Heap Dump 功能獲取。

2.3 分析堆轉儲文件

打開 MAT 之後,加載 dump 文件,差不多就下面這樣的界面:

常用的兩個功能:Histogram、 Leak Suspects

2.3.1 Histogram

Histogram 可以列出內存中的對象,對象的個數及其內存大小,可以用來定位哪些對象在 Full GC 之後還活着,哪些對象佔大部分內存。

  • Class Name:類名稱,Java 類名。
  • Objects:類的對象的數量,這個對象被創建了多少個。
  • Shallow Heap:對象本身佔用內存的大小,不包含其引用的對象內存,實際分析中作用不大。常規對象(非數組)的 Shallow Size 由其成員變量的數量和類型決定。數組的 Shallow Size 由數組元素的類型(對象類型、基本類型)和數組長度決定。對象成員都是些引用,真正的內存都在堆上,看起來是一堆原生的 byte[], char[], int[],對象本身的內存都很小。
  • Retained Heap:計算方式是將 Retained Set(當該對象被回收時那些將被 GC 回收的對象集合)中的所有對象大小疊加。或者說,因爲 X 被釋放,導致其它所有被釋放對象(包括被遞歸釋放的)所佔的 heap 大小。Retained Heap 可以更精確的反映一個對象實際佔用的大小。

Retained Heap 例子:一個 ArrayList 對象持有 100 個對象,每一個佔用 16 bytes,如果這個 list 對象被回收,那麼其中 100 個對象也可以被回收,可以回收 16*100 + X 的內存,X 代表 ArrayList 的 shallow 大小。


在上述列表中選擇一個 Class,右鍵選擇 List objects > with incoming references,在新頁面會顯示通過這個 class 創建的對象信息。


繼續選擇一個對象,右鍵選擇 Path to GC Roots > **** ,通常在排查**內存泄漏(一般是因爲存在無效的引用)**的時候,我們會選擇 exclude all phantom/weak/soft etc.references,意思是查看排除虛引用/弱引用/軟引用等的引用鏈,因爲被虛引用/弱引用/軟引用的對象可以直接被 GC 給回收,我們要看的就是某個對象否還存在 Strong 引用鏈(在導出 Heap Dump 之前要手動觸發 GC 來保證),如果有,則說明存在內存泄漏,然後再去排查具體引用。

這時會拿到 GC Roots 到該對象的路徑,通過對象之間的引用,可以清楚的看出這個對象沒有被回收的原因,然後再去定位問題。如果上面對象此時本來應該是被 GC 掉的,簡單的辦法就是將其中的某處置爲 null 或者 remove 掉,使其到 GC Root 無路徑可達,處於不可觸及狀態,垃圾回收器就可以回收了。反之,一個存在 GC Root 的對象是不會被垃圾回收器回收掉的。

2.3.2 Leak Suspects

Leak Suspects 可以自動分析並提示可能存在的內存泄漏,可以直接定位到 Class 及對應的行數。


比如:這裏問題一的描述,列出了一些比較大的實例。點擊 Details 可以看到細節信息,另外還可點擊 See stacktrace 查看具體的線程棧信息(可直接定位到具體某個類中的方法)。

在 Details 詳情頁面 Shortest Paths To the Accumulation Point 表示 GC root 到內存消耗聚集點的最短路徑,如果某個內存消耗聚集點有路徑到達 GC root,則該內存消耗聚集點不會被當做垃圾被回收。

實戰:在某項目中,其中幾個 Tomcat 響應特別慢,打開 Java VisualVM 觀察 Tomcat(pid xxx)-Visual GC 發現 Spaces-Old 升高,Graphs-GC Time 比較頻繁且持續時間長、有尖峯(重啓後過段時間又出現了),最後通過 Leak Suspects 中的 See stacktrace 定位到某個查詢接口,仔細排查代碼後發現有個 BUG:在特定查詢條件下會一次性查詢幾萬的數據出來(因爲髒數據),處理過後恢復正常。

2.3.3 內存快照對比

爲了更有效率的找出內存泄露的對象,一般會獲取兩個堆轉儲文件(先 dump 一個,隔段時間再 dump 一個),通過對比後的結果可以很方便定位。


掃碼關注微信公衆號 程序員35 ,獲取最新技術乾貨,暢聊 #程序員的35,35的程序員# 。獨立站點:https://cxy35.com

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章