java應用監測(6)-第三方內存分析工具MAT

tags: java,troubleshooting,monitor,matphp


一句話歸納:MAT是一個強大的內存分析工具,能夠快捷、有效地幫助咱們找到內存泄露,減小內存消耗分析工具,下文將進行講解。html

1 引言

以前的文章有提過,內存中堆的使用狀況是應用性能監測的重點,而對於堆的快照,能夠dump出來進一步分析,總的來講,通常咱們對於堆dump快照有三種方式:java

  • 添加啓動參數發生OOM時自動dump: java應用的啓動參數通常最好都加上-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=logs/heapdump.hprof,即在發生OOM時自動dump堆快照,但這種方式至關來講是滯後的(須要等到發生OOM後)。
  • 使用命令按需手動dump: 咱們也可使用jmap -dump:format=b,file=HeapDump.hprof <pid>工具手動進行堆dump和線程dump
  • 使用工具手動dump: 如上一篇文章jconsolejvisualvm的講解中講到,jvisualvm有提供dump堆快照的功能,點擊一下便可。

那麼,對於dump出來的文件,如何對它們進行分析?jvisualvm能夠直接裝入快照文件進行分析,而本文所介紹的MAT,相對來講內存分析功能更強大,它能夠自動檢測有可能發生問題(特別是內存溢出、內存泄露)的地方,也能夠詳細查看類內存佔用狀況,方法級的調用狀況等,是一個分析內存情況的不可多得的工具。git

2 MAT工具介紹

MAT(Memory Analyzer tool)是一款內存分析器工具,它是一個快速且功能豐富的堆內存分析器,幫助咱們查找內存泄漏和分析高內存消耗問題。官方網站是:https://www.eclipse.org/mat/,有興趣能夠上去看一下。使用MAT,能夠輕鬆實現如下功能:程序員

  • 找到最大的對象,由於MAT提供顯示合理的累積大小(retained size
  • 探索對象圖,包括inboundoutbound引用,即引用此對象的和此對象引出的。
  • 查找沒法回收的對象,能夠計算從垃圾收集器根到相關對象的路徑
  • 找到內存浪費,好比冗餘的String對象,空集合對象等等

3 MAT工具安裝

MAT安裝有兩種方式,一種是以eclipse插件方式安裝,一種是獨立安裝。在MAT官方文檔中有相應的安裝文件下載,下載地址爲:https://www.eclipse.org/mat/downloads.phpgithub

  • 若使用eclipse插件安裝,help -> install new soft點擊ADD,在彈出框中添加插件地址:http://download.eclipse.org/mat/1.9.0/update-site/,也能夠直接在下載頁面下載離線插件包,以離線方式安裝。
  • 獨立安裝,windows下,根據系統直接下載Windows (x86)Windows (x86_64),下載時能夠選擇適合本身的鏡像,雙擊安裝便可。

4 MAT工具使用

MAT定位是內存分析工具,它的主要功能就是對內存快照進行分析,幫助咱們找到有可能內存溢出或內存泄漏的地方,所以,找到佔用內存大的對象和找出沒法回收的對象是其主要目的。MAT官方文檔,地址以下:https://help.eclipse.org/2019-06/index.jsp?topic=/org.eclipse.mat.ui.help/welcome.html,對MAT的使用進行描述,有興趣的同窗能夠上去看看。下面主要對MAT的經常使用概念、經常使用的功能進行介紹。spring

下文中,以java-monitor-example(https://github.com/mianshenglee/my-example/tree/master/java-monitor-example)爲例,此示例是一個簡單的spring boot工程,裏面的一個controller中的user/oom接口調用的service對象經過List成員不斷地添加User對象,最終致使OOM的發生,應用的啓動參數是-Xms64m -Xmx64m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${APP_HOME}/logs/heapdump.hprofbootstrap

4.1 MAT相關概念說明

4.1.1 內存泄漏與內存溢出

  • 內存泄露:對象已經沒用了(不被任何程序邏輯所須要),還存在被根元素引用的狀況,沒法經過垃圾收集器進行自動回收,須要經過找出泄漏的代碼位置和緣由,纔好肯定解決方案;
  • 內存溢出:內存中的對象都還存活着,JVM的堆分配空間不足,須要檢查堆設置大小(-Xmx與-Xms),代碼是否存在對象生命週期太長、持有狀態時間過長的狀況。

4.1.2 引用(強引用,軟引用,弱引用,虛引用)

  • Strong Ref(強引用):強可達性的引用,對象保存在內存中,只有去掉強可達,對象才被回收,一般咱們編寫的代碼都是Strong Ref。windows

  • Soft Ref(軟引用):對應軟可達性,只要有足夠的內存,就一直保持對象,直到發現內存吃緊且沒有Strong Ref時纔回收對象。通常可用來實現緩存,經過java.lang.ref.SoftReference類實現。數組

  • Weak Ref(弱引用):比Soft Ref更弱,當發現不存在Strong Ref時,馬上回收對象而沒必要等到內存吃緊的時候。經過java.lang.ref.WeakReference和java.util.WeakHashMap類實現。

  • Phantom Ref(虛引用):根本不會在內存中保持任何對象,你只能使用Phantom Ref自己。通常用於在進入finalize()方法後進行特殊的清理過程,經過 java.lang.ref.PhantomReference實現。

4.1.3 shallow heapretained heap

  • shallow heap:對象自己佔用內存的大小,也就是對象頭加成員變量(不是成員變量的值)的總和,如一個引用佔用32或64bit,一個integer佔4bytes,Long佔8bytes等。如簡單的一個類裏面只有一個成員變量int i,那麼這個類的shallo size是12字節,由於對象頭是8字節,成員變量int是4字節。常規對象(非數組)的Shallow size有其成員變量的數量和類型決定,數組的shallow size有數組元素的類型(對象類型、基本類型)和數組長度決定。
  • retained heap:若是一個對象被釋放掉,那會由於該對象的釋放而減小引用進而被釋放的全部的對象(包括被遞歸釋放的)所佔用的heap大小,即對象X被垃圾回收器回收後能被GC從內存中移除的全部對象之和。相對於shallow heap,Retained heap能夠更精確的反映一個對象實際佔用的大小(若該對象釋放,retained heap均可以被釋放)。

4.1.4 outgoing referencesincoming references

  • outgoing references :表示該對象的出節點(被該對象引用的對象)。
  • incoming references :表示該對象的入節點(引用到該對象的對象)。

4.1.5 Dominator Tree

將對象樹轉換成Dominator Tree能幫助咱們快速的發現佔用內存最大的塊,也能幫咱們分析對象之間的依賴關係。Dominator Tree有如下幾個定義:

  • 對象X Dominator(支配)對象Y,當且僅當在對象樹中全部到達Y的路徑都必須通過X
  • 對象Y的直接Dominator,是指在對象樹中距離Y最近的Dominator
  • Dominator tree利用對象樹構建出來。在Dominator tree中每個對象都是他的直接Dominator的子節點。

對象樹和Dominator tree的對應關係以下:

如上圖,由於A和B都引用到C,因此A釋放時,C內存不會被釋放。因此這塊內存不會被計算到A或者B的Retained Heap中,所以,對象樹在轉換成Dominator tree時,會A、B、C三個是平級的。

4.1.6 Garbage Collection Roots(GC root)

在執行GC時,是經過對象可達性來判斷是否回收對象的,一個對象是否可達,也就是看這個對象的引用連是否和GC Root相連。一個GC root指的是能夠從堆外部訪問的對象,有如下緣由可使一個對象成爲GC root對象。

  • System Class: 經過bootstrap/system類加載器加載的類,如rt.jar中的java.util.*
  • JNI Local: JNI方法中的變量或者方法形參
  • JNI Global:JNI方法中的全局變量
  • Thread Block:線程裏面的變量,一個活着的線程裏面的對象確定不能被回收
  • Thread:處於激活狀態的線程
  • Busy Monitor:調用了wait()、notify()方法,或者是同步對象,例如調用synchronized(Object) 或者進入一個synchronized方法後的當前對象
  • Java Local:本地變量,例如方法的輸入參數或者是方法內部建立的仍在線程堆棧裏面的對象
  • Native Stack:Java 方法中的變量或者方法形參.
  • Finalizable:等待運行finalizer的對象
  • Unfinalized:有finalize方法,但未進行finalized,且不在finalizer隊列的對象。
  • Unreachable:經過其它root都不可達的對象,MAT會把它標記爲root以便於分析回收。
  • Java Stack Frame:java棧幀
  • Unknown

4.2 Leak Suspects自動分析泄漏

當發生OOM獲取到heapdump.hprof文件或者手動dump出文件後,使用MAT打開文件。打開後默認會提示是否進行內存泄漏檢測報告(若是打開Dump時跳過了的話,也能夠從其它入口進入工具欄上的 Run Expect System Test -> Leak Suspects),以下圖所示:

選擇是後進入報告內容,此報告內容會幫咱們分析的可能有內存泄露嫌疑的地方,它會把累積內存佔用較大的經過餅狀圖顯示出來。以下圖所示:

如上圖,報告已指出UserService佔用了76.73%的內存,而這些內存都在Object[]這個數組中。所以,很大一種多是這個對象中的數組數量過大。點擊Details能夠查看更詳細的內容:

在此詳細而,Shortest Paths To the Accumulation Point能夠顯示出到GC roots的最短路徑,由此能夠分析是因爲和哪一個GC root相連致使當前Retained Heap佔用至關大的對象沒法被回收,本示例中,GC root是線程的本地變量(java local)。Accumulated Objects in Dominator TreeDominator Tree爲視圖,能夠方便的看出受當前對象「支配」的對象中哪一個佔用Retained Heap比較大。圖中可看出對象都在ArrayList中,而ArrayList下有Object數組,數組下是User對象。此能夠知道問題出在哪裏了。須要針對這個位置,查看代碼,找出致使這個數組存儲的User過量的緣由。

注:在原始堆轉儲文件的目錄下,MAT已經將報告的內容壓縮打包到一個zip文件,命名爲「xxx_Leak_Suspects.zip」,整個報告是一個HTML格式的文件,能夠用瀏覽器直接打開查看,能夠方便進行報告分發、分享。

4.3 histogram視圖查看對象個數與大小

點擊Overview頁面Actions區域內的「Histogram視圖」或點擊工具欄的「histogram」按鈕,能夠顯示直方圖列表,它以Class類的維度展現每一個Class類的實例存在的個數、 佔用的Shallow heapRetained內存大小,能夠分別排序顯示。以下圖所示:

Shallow HeapRetained Heap的概念前面已經講過。 對於java的對象,其成員基本都是引用。真正的內存都在堆上,看起來是一堆原生的byte[], char[], int[],所以若是隻看對象自己的內存,數量都很小,而多數狀況下,在Histogram視圖看到實例對象數量比較多的類都是一些基礎類型(一般都排在前面),如char[]、String、byte[],因此僅從這些是沒法判斷出具體致使內存泄露的類或者方法的。從上圖中,能夠直接看到User對象的數量不少,有時不容易看出來的,可使用工具欄中的group result by 能夠以super class,class loader等排序,須要特別注意自定義的classLoader,以下圖:

一般,若是Histogram視圖展現的數量多的實例對象不是基礎類型,而是用戶自定義的類或者有嫌疑的類,就要重點關注查看。想進一步分析的,能夠右鍵,選擇使用List objectsMerge Shortest Paths to GC roots等功能繼續鑽取數據。其中list objects分別有outgoing referencesincoming references,能夠找出由這個對象出去的引用及經過哪些引用到這個對象的。Merge Shortest Paths to GC roots能夠排除掉全部不是強引用的,找到這個對象的到GC root的引用路徑。最終目的就是找到佔用內存最大對象和沒法回收的對象,計算從垃圾收集器根到相關對象的路徑,從而根據這個對象路徑去檢查代碼,找出問題所在。

4.4 Dominator Tree視圖

點擊Overview頁面Actions區域內的「Dominator Tree視圖」或點擊工具欄的「open Dominator Tree」按鈕,能夠進入 Dominator Tree視圖。該視圖以實例對象的維度展現當前堆內存中Retained Heap佔用最大的對象,以及依賴這些對象存活的對象的樹狀結構。以下圖:

視圖展現了實例對象名、Shallow Heap大小、Retained Heap大小、以及當前對象的Retained Heap在整個堆中的佔比。該圖是樹狀結構的,當上一級的對象被回收,那麼,它引用的子對象都會被回收,這也是Dominator的意義,當父節點的回收會致使子節點也被回收。經過此視圖,能夠很方便的找出佔用Retained Heap內存最多的幾個對象,並表示出某些objects的是由於哪些objects的緣由而存活。本示例中,能夠看出是因爲UserService中的ArrayList引用的數組存儲過量的User對象。

4.5 線程視圖查看線程棧運行狀況

點擊工具欄的「齒輪」按鈕,能夠打開Thread Overview視圖,能夠查看線程的棧幀信息,包括線程對象/線程棧信息、線程名、Shallow HeapRetained Heap、類加載器、是否Daemon線程等信息。結合內存Dump分析,看到線程棧幀中的本地變量,在左下方的對象屬性區域還能看到本地變量的屬性值。如按上面的示例(shortest paths to GC root),知道是因爲線程Thread-12GC-root佔用內存大,在線程視圖中,就能夠着重看它的屬性狀況,以下圖:

由上圖能夠看到此線程調用WithOOM方法,使用了變量UserService,而變量使用了userList,它包含了大量的User對象,佔用retained heap很大。

5 總結

對於java應用的內存分析,須要對java應用的內存進行dump操做,生成內存快照後,使用MAT進行分析,找出大對象,找到內存泄漏或溢出的地方,從而分析代碼,解決問題。本文對MAT的使用場景,基本概念,安裝、使用進行了詳細介紹,你們能夠本身安裝一下,寫個小示例或者拿本文中的示例,實踐一下。經過本文,但願能夠幫助你們更方便,更有效率地分析內存,解決java應用的內存故障問題。

參數資料

  • MAT官網:https://www.eclipse.org/mat/
  • MAT下載:http://www.eclipse.org/mat/downloads.php
  • MAT使用手冊:https://help.eclipse.org/2019-06/index.jsp?topic=/org.eclipse.mat.ui.help/welcome.html
  • Shallow and retained sizes:https://www.yourkit.com/docs/java/help/sizes.jsp
  • 使用Eclipse Memory Analyzer Tool(MAT)分析線上故障(一) - 視圖&功能篇:https://www.cnblogs.com/trust-freedom/p/6744948.html
  • 示例代碼地址:https://github.com/mianshenglee/my-example/tree/master/java-monitor-example

相關閱讀

相關文章
相關標籤/搜索