儘管JVM提供了自動內存管理的機制,試圖降低程序員的開發門檻,確實也實現了這一目標,在日常開發中,我們一般都不需要關心對象的內存釋放。JVM大部分都是使用trace演算法來判斷一個對象是否該被回收,那麼JVM只能回收那些從gc roots不可達的對象。

如果我們在使用某些大的對象、集合對象或者一些三方包里的資源,忘記及時釋放資源的話,還是會造成JVM的內存泄漏或內存浪費的問題。因此,如果想成為更高階的Java開發工程師,我們需要了解常見的問題排查的辦法和工具,這個系列的文章,準備介紹一個用來做JVM堆內存分析的工具——MAT(Memory Aanlysis Tool)。

MAT的官網在:eclipse.org/mat/,可以看下它的介紹——MAT是一款高性能、具備豐富功能的Java堆內存分析工具,可以用來排查內存泄漏和內存浪費的問題。

一、安裝和裝設置

1.1 mac安裝

MAT 支持兩種安裝方式,一種是"單機版「的,也就是說用戶不必安裝 Eclipse IDE 環境,MAT 作為一個獨立的 Eclipse RCP 應用運行;另一種是」集成版「的,也就是說 MAT 也可以作為 Eclipse IDE 的一部分,和現有的開發平台集成。

這裡我們考慮獨立安裝,在觀望的下載頁面,選擇mac os版本的安裝文件下載即可。

安裝遇到的坑

  1. 啟動直接報錯,系統默認的workspace是只讀的,更換掉即可。怎麼更換呢,在文件/Applications/mat.app/Contents/Eclipse/MemoryAnalyzer.ini中進行修改。

  1. 啟動後,UI界面沒反應,參考:eclipse.org/forums/inde,換個包即可。這個問題我遇到過很多次。

1.2 mat的設置

配置mat的堆內存大小

我的電腦是8C16G的,那理論上分析10G的堆文件沒問題,但是MAT默認的配置沒有這麼大,需要在/Applications/mat.app/Contents/Eclipse/MemoryAnalyzer.ini文件中進行修改。如下圖所示,我將我的MAT自己的運行時堆內存配置成了6G。

配置MAT的使用

MAT的配置頁面可以從Window——>Preferences找到,如下圖所示。

MAT的一般配置有幾個選項

  1. Keep unreachable objects:如果勾選這個,則在分析的時候會包含dump文件中的不可達對象;
  2. Hide the getting started wizard:隱藏分析完成後的首頁,控制是否要展示一個對話框,用來展示內存泄漏分析、消耗最多內存的對象排序。
  3. Hide popup query help:隱藏彈出查詢幫助,除非用戶通過F1或Help按鈕查詢幫助。
  4. Hide Welcome screen on launch:隱藏啟動時候的歡迎界面
  5. Bytes Display:設置分析結果中內存大小的展示單位

可以看出,MAT不僅支持HPROF文件的分析,還支持DTFJ文件的分析。一般sun公司系列的JVM生成的dump文件都是HPROF格式的,IBM的JVM生成的dump文件時DTFJ格式的。

二、基本概念

Heap Dump

Heap Dump是Java進程在某個時刻的內存快照,不同JVM的實現的Heap Dump的文件格式可能不同,進而存儲的數據也可能不同,但是一般來說。

Heap Dump中主要包含當生成快照時堆中的java對象和類的信息,主要分為如下幾類:

  • 對象信息:類名、屬性、基礎類型和引用類型
  • 類信息:類載入器、類名稱、超類、靜態屬性
  • gc roots:JVM中的一個定義,進行垃圾收集時,要遍歷可達對象的起點節點的集合
  • 線程棧和局部變數:快照生成時候的線程調用棧,和每個棧上的局部變數

Heap Dump中沒有包含對象的分配信息,因此它不能用來分析這種問題:一個對象什麼時候被創建、一個對象時被誰創建的。

Shallow vs. Retained Heap

Shallow heap是一個對象本身佔用的堆內存大小。一個對象中,每個引用佔用8或64位,Integer佔用4位元組,Long佔用8位元組等等。

Retained set,對於某個對象X來說,它的Retained set指的是——如果X被垃圾收集器回收了,那麼這個集合中的對象都會被回收,同理,如果X沒有被垃圾收集器回收,那麼這個集合中的對象都不會被回收。

Retained heap,對象X的Retained heap指的時候它的Retained set中的所有對象的Shallow si的和,換句話說,Retained heap指的是對象X的保留內存大小,即由於它的存活導致多大的內存也沒有被回收。

leading set,對象X可能不止有一個,這些對象統一構成了leading set。如果leading set中的對象都不可達,那麼這個leading set對應的retained set中的對象就會被回收。一般有以下幾種情況:

  1. 某個類的所有實例對象,這個類對象就是leading object
  2. 某個類記載器載入的所有類,以及這些類的實例對象,這個類載入器對象就是leading object
  3. 一組對象,要達到其他對象的必經路徑上的對象,就是leading object

在下面這張圖中,A和B是gc roots中的節點(方法參數、局部變數,或者調用了wait()、notify()或synchronized()的對象)等等。可以看出,E的存在,會導致G無法被回收,因此E的Retained set是E和G;C的存在,會導致E、D、F、G、H都無法被回收,因此C的Retined set是C、E、D、F、G、H;A和B的存在,會導致C、E、D、F、G、H都無法被回收,因此A和B的Retained set是A、B、C、E、D、F、G、H。

Dominator Tree

MAT根據堆上的對象引用關係構建了支配樹(Dominator Tree),通過支配樹可以很方便得識別出哪些對象佔用了大量的內存,並可以看到它們之間的依賴關係。

如果在對象圖中,從gc root或者x上游的一個節點開始遍歷,x是y的必經節點,那麼就可以說x支配了y(dominate)。

如果在對象圖中,x支配的所有對象中,y的距離最近,那麼就可以說x直接支配(immediate dominate)y。

支配樹是基於對象的引用關係圖建立的,在支配樹中每個節點都是它的子節點的直接支配節點。基於支配樹可以很清楚得看到對象之間的依賴關係。

現在看個例子,在下面這張圖中

  1. x節點的子樹就是所有被x支配的節點集合,也正式x的retained set;
  2. 如果x是y的直接支配節點,那麼x的支配節點也可以支配y
  3. 支配樹中的邊跟對象引用圖中的引用關係並不是一一對應的。

Garbage Collection Roots

在MAT中,gc roots的概念跟研究垃圾收集演算法時候的概念稍微有點不同。gc roots中的對象,是指那些可以從堆外訪問到的對象的集合。如果一個對象符合下面這些場景中的一個,就可以被認為是gc roots中的節點:

  1. System Class:由bootstrap classloader載入的類,例如rt.jar,裡面的類的包名都是java.util.*開頭的。
  2. JNI Local:native代碼中的局部變數,例如用戶編寫的JNI代碼或JVM內部代碼。
  3. JNI Global:native代碼中的全局變數,例如用戶編寫的JNI代碼或JVM內部代碼。
  4. Thread Block:被當前活躍的線程鎖引用的對象。
  5. Thread:正在存活的線程
  6. Busy Monitor:調用了wait()、notify()或synchronized關鍵字修飾的代碼——例如synchronized(object)synchronized方法。
  7. Java Local:局部變數。例如函數的輸入參數、正在運行的線程棧里創建的對象。
  8. Native Stack:native代碼的輸入或輸出參數,例如用戶定義的JNI代碼或JVM的內部代碼。在文件/網路IO方法或反射方法的參數。
  9. Finalizable:在finalize隊列中等待它的finalizer對象運行的對象。
  10. Unfinalized:重載了finalize方法,但是還沒有進入finalize隊列中的對象。
  11. Unreachable:從任何gc roots節點都不可達的對象,在MAT中將這些對象視為root節點,如果不這麼做,就不能對這些對象進行分析。
  12. Java Stack Frame:Java棧幀,用於存放局部變數。只在dump文件被解析的時候會將java stack frame視為對象。
  13. Unknown:沒有root類型的對象。有些dump文件(例如IBM的Portable Heap Dump)沒有root信息。

三、獲取Dump文件

  1. 通過MAT生成dump文件 通過這個路徑找到生成dump文件的對話框

選擇一個進程,點擊finish即可

  1. 通過jmap命令生成dump文件
  • 命令格式:jmap -dump:live,format=b,file=heap.bin <pid>
  • 注意:如果要保留heapdump中的不可達對象,則需要把」:live「去掉,即使用命令」jmap -dump,format=b,file=heap.bin <pid>「

  1. 通過設置JVM參數自動生成 使用-XX:+HeapDumpOnOutOfMemoryError這個JVM參數,在Java進程運行過程中發生OOM的時候就會生成一個heapdump文件,並寫入到指定目錄,一般用-XX:HeapDumpPath=${HOME}/logs/test來設置。


本號專註於後端技術、JVM問題排查和優化、Java面試題、個人成長和自我管理等主題,為讀者提供一線開發者的工作和成長經驗,期待你能在這裡有所收穫。


推薦閱讀:
相关文章