在日常的開發工作中,遇到生産環境報OOM的問題時,你首先會想到采用哪些方式并使用什麼樣的工具對OOM問題進行分析,定位和解決呢?
實際現場環境無非有這麼兩種,第一種,如果項目所在的生産伺服器不允許導出日志或者資料之類的,那就隻好依靠線上操作相關的JVM指令進行分析排查;第二種,如果條件允許,則可以外接JVM相關的排查工具,直接連接配接生産的項目程序,進行實時分析
第二種方式下,通常可利用JDK自帶的一些工具,比如jconsole,jmap等工具連接配接程序,但更多的場景是,問題已經發生了,也就是犯罪現場出現了,又不允許随便破壞環境,更通常的做法是,導出日志,利用第三方工具進行排查
本篇将針對這一點,簡單介紹下一款強大的dump日志分析工具,Eclipse Memory Analyzer,也稱作MAT
MAT工具是一款強大的Java堆記憶體分析工具,可用于查找記憶體洩露以及檢視記憶體消耗情況,便于開發或運維人員快速定位記憶體溢出或記憶體洩露問題
MAT基于eclipse開發,可以單獨使用,也可以以插件形式嵌入到開發工具中,是一款免費的性能分析工具,使用起來很友善,官網下載下傳位址:https://projects.eclipse.org/projects/tools.mat
MAT下載下傳後,無需安裝,解壓之後,輕按兩下下面的exe檔案即可打開,

打開之後,是一個非常簡潔幹淨的界面
以上是使用MAT工具分析dump檔案之前的準備工作,下面來具體介紹下開發中的常用功能
1、MAT導入dump檔案
使用下面這段程式,通過在啟動參數中配置JVM的指令,這裡主要是指定該類的運作時記憶體,
然後配置類的運作參數
運作上面的程式,然後,在本地的CMD視窗中,通過導出dump檔案的指令導出程式運作時的hprof檔案
使用: File ——> Open File ,導入剛剛生成的oomtest5這個dump檔案,
導入成功之後首先來到上面這個界面,我們先不着急如何分析問題,先快速了解下面闆上各個元件的用法,友善後面的學習
2、MAT基本功能說明
OverView
OverView功能是導入了dump檔案之後,對可能出現的問題做了一個整體性的分析概述,首先呈現在眼前的是以一個形狀圖的方式,快速展現了該檔案中dump檔案大小,以及類、對象和類加載器的數量
當光标移動到藍色區域時候,會呈現該日志檔案的主要線程,類加載器,深堆,淺堆資訊
分析報告,點選此處,可以檢視dump檔案的各類參數報告
以Heap Dump Overview為例,以表格的形式呈現出目前的dump檔案中,已建立的對象個數,class的格式,類加載器的個數等,反應的是一個概覽資訊
在該菜單下,另一個比較好用的就是這個Leak Suspects功能,MAT分析工具會根據你導入的dump檔案,快速生成一份懷疑報告,将可能出現記憶體洩露的點展示出來,便于開發或運維人員進行問題定位
Histogram 列舉類的執行個體資訊
當點選進去之後,為我們呈現出dump檔案中,已經建立的主要的對象資訊,預設按照對象的個數進行排序,而這個排序,多少也反映出在目前的dump檔案中,那些排在前面的數量最多的對象可能是我們分析問題的關鍵入口
頂部的可以支援對象名稱的模糊比對,比如搜尋Picture,可以快速找到我們需要的對象名稱
在該菜單下,還提供了分組功能,友善快速定位類的資訊
如果還能根據字段的名稱進行排序,也是快速定位到需要查找到的對象的一種方式
在該欄,另外有2個參數值得注意,就是列舉出來的Shallow Heap的數量和Retained Heap的數量,即淺堆和深堆的數量,通常來說,對某個具體的對象來說,深堆的數量是超過淺堆的數量的,這個和JVM對象的底層原理有一定關系,
由于這裡是分析OOM時候的一個關鍵點,這裡就對淺堆和深堆做一點補充解釋
淺堆:指的是一個對象所消耗的記憶體(不包括内部引用的對象大小)
我們知道,對象在建立出來時,需要JVM在堆記憶體中開辟一定的空間存儲實際對象,然後通路時候,通過指針找到對象,但是在真實的場景中,一個對象可能引用(持有)了其他對象,或者是該對象被其他對象引用,或者通過更深的層級引用等,這就造成了,當這個對象的生命周期的結束之後,垃圾回收期要回收該對象時,不得不去找到這個對象的完整引用關系,這個涉及到底層的GC就不進一步展開了
要說明的是,在JVM中,存在一個叫做保留集的概念,對某個特定的對象來說,該對象的保留集指的是當該對象被垃圾回收後,可以釋放的所有對象集合(包括該對象自身),即對象的保留集可認為是隻能通過該對象被直接或者間接通路到的所有對象集合,通俗來說,就是該對象所持有的所有對象集合
深堆:某對象的保留集中所有對象的淺堆之和
這就解釋了為什麼深堆對象的個數要比淺堆多了
分析這個的意義在于,實際開發過程中,可能因為編碼的習慣不好,導緻某些類中,對象的引用鍊條特别長,層級也很深,最後甚至連自己都不一定能搞清楚那些對象是實際在使用的,假如正好有那麼一些對象實際上并沒有使用,但是在某些循環中大量建立,尤其是大對象,在這種情況下,很容易造成GC過程的失敗最終引發OOM,從MAT中的這個展現的資料來看,對于快速定位那些數量較多的對象還是很有幫助的
最後再對該菜單功能中的2個實用的點做下補充,如下所示,右鍵某個對象的List objects這一欄,顯示出兩個功能标簽,outgoing references 和 incoming references
outgoing references :表示的是目前對象引用的外部對象集合
incoming references: 引用目前對象的外部對象的集合
上面的2個名額可以分析目前對象的對象引用鍊
排除該對象的弱引用,虛引用,通過點選這裡,隻篩選出目前對象被那些對象進行了強引用,在OOM的分析中,可以對此判斷是哪些對象對于該對象的引用造成了對象無法回收的情況,也是定位問題的一個很好的點
那麼點選完畢之後,可以發現Picture對象被一個集合給持有着,如果該集合長期無法被垃圾回收的話,最後一定會造成OOM
Thread Overview (線程分析概覽)
點選該菜單,展示出目前dump檔案中,所有的線程資訊,如上圖所示,展示出了所有的線程,主線程,以及各個線程中的淺堆和深堆占用的大小,類加載器,是否守護線程等資訊
以main線程為例,列舉出了裡面的成員變量的資訊,比如在目前的main線程中,有2個成員變量,其中一個集合裡面包括了很多Picture對象
從開始的leak suspects中,也給出了初步的判斷,報告說的是在main線程中,有一個對象的大小占據了堆記憶體的大小的93%,說明在目前這一時刻,某個對象有可能成為OOM的原因
Domainator Tree (支配樹)
MAT工具提供了一個稱為支配樹的對象圖,支配樹展現了對象執行個體之間的支配關系,在對象引用關系圖中,所有指向對象B的路徑都經過對象A,則認為對象A支配對象B,這個涉及到算法中的圖論,不做過多展開
了解支配樹的目的是,在我們分析dump檔案時,可以通過支配樹,清楚的知道某個對象引用的對象情況(對象支配的其他對象),了解這一層,在定位目前對象是否能進行GC以及為何不能進行GC時,就有了前提,來看下面這段代碼
将頂部注釋中的參數配置在啟動參數中,讓程式運作完畢之後,生成一個dump檔案
然後使用MAT打開該檔案,從Thread Overview中,可以清楚的看到,目前存在的3個對象,Tom,Jerry和Lily
以Tom為例,線程Overview中,Tom對象中包含了24個元素,這個數量包括2部分,一部分是能被3除的那些對象,而另一部分則是還能被5和7除的那些對象,
我們不妨再看下Jerry中的集合裡面的對象個數,數量顯然要少一些
有興趣的同學可以再看一下Lily的,數量就更少了,這個說明的一個問題就是,3個對象的集合中,有一些對象是被共同引用着
而對象支配樹中,可以将各對象持有的真實對象,以及引用對象都列舉出來,就能很友善的檢視各自對象的真實引用情況,進而定位分析那些有可能存在的被多個對象引用但實際并沒有使用的對象,造成的無法被垃圾回收的情況
比如從支配樹中,通過Thread定位到Tom,這時候發現集合中的對象個數隻有23個,比上面少了2個,這個就是在支配樹中,該對象真實支配的對象個數,多出來的2個被Jerry或Lily對象引用了
當Tom這個對象在進行垃圾回收的時候,實際上能被回收的隻有23個,另外2個由于被其他對象引用,而無法被回收
現實的情況是,如果類似的情況,很多個對象都共同持有着對一部分對象的引用,在高峰期時,一旦JVM的記憶體比較吃緊的時候,可能會成為OOM發生的因素,需要格外注意
限于篇幅原因,本篇到這裡就要結束了,本篇主要從使用上介紹了MAT這款工具的基本使用,以及分析OOM時的幾個技巧,希望對看到的同學有用,本篇到此結束,最後感謝觀看!