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