您好,登錄后才能下訂單哦!
java 第三方內存分析工具MAT怎么用,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
tags: java,troubleshooting,monitor,mat
一句話概括:
MAT
是一個強大的內存分析工具,可以快捷、有效地幫助我們找到內存泄露,減少內存消耗分析工具,下文將進行講解。
之前的文章有提過,內存中堆的使用情況是應用性能監測的重點,而對于堆的快照,可以dump出來進一步分析,總的來說,一般我們對于堆dump快照有三種方式:
添加啟動參數發生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: 如上一篇文章對jconsole
及jvisualvm
的講解中講到,jvisualvm有提供dump堆快照的功能,點擊一下即可。
那么,對于dump出來的文件,如何對它們進行分析?jvisualvm
可以直接裝入快照文件進行分析,而本文所介紹的MAT
,相對來說內存分析功能更強大,它可以自動檢測有可能發生問題(特別是內存溢出、內存泄露)的地方,也可以詳細查看類內存占用情況,方法級的調用情況等,是一個分析內存狀況的不可多得的工具。
MAT(Memory Analyzer tool)是一款內存分析器工具,它是一個快速且功能豐富的堆內存分析器,幫助我們查找內存泄漏和分析高內存消耗問題。官方網站是:https://www.eclipse.org/mat/
,有興趣可以上去看一下。使用MAT,可以輕松實現以下功能:
找到最大的對象,因為MAT提供顯示合理的累積大小(retained size
)
探索對象圖,包括inbound
和outbound
引用,即引用此對象的和此對象引出的。
查找無法回收的對象,可以計算從垃圾收集器根到相關對象的路徑
找到內存浪費,比如冗余的String對象,空集合對象等等
MAT
安裝有兩種方式,一種是以eclipse
插件方式安裝,一種是獨立安裝。在MAT
的官方文檔中有相應的安裝文件下載,下載地址為:https://www.eclipse.org/mat/downloads.php
若使用eclipse插件安裝,help -> install new soft
點擊ADD
,在彈出框中添加插件地址:http://download.eclipse.org/mat/1.9.0/update-site/
,也可以直接在下載頁面下載離線插件包,以離線方式安裝。
獨立安裝,windows下,根據系統直接下載Windows (x86)
或Windows (x86_64)
,下載時可以選擇適合自己的鏡像,雙擊安裝即可。
MAT
定位是內存分析工具,它的主要功能就是對內存快照進行分析,幫助我們找到有可能內存溢出或內存泄漏的地方,因此,找到占用內存大的對象和找出無法回收的對象是其主要目的。MAT官方文檔,地址如下:https://help.eclipse.org/2019-06/index.jsp?topic=/org.eclipse.mat.ui.help/welcome.html
,對MAT
的使用進行描述,有興趣的同學可以上去看看。下面主要對MAT
的常用概念、常用的功能進行介紹。
下文中,以
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.hprof
。
內存泄露:對象已經沒用了(不被任何程序邏輯所需要),還存在被根元素引用的情況,無法通過垃圾收集器進行自動回收,需要通過找出泄漏的代碼位置和原因,才好確定解決方案;
內存溢出:內存中的對象都還存活著,JVM的堆分配空間不足,需要檢查堆設置大小(-Xmx與-Xms),代碼是否存在對象生命周期太長、持有狀態時間過長的情況。
Strong Ref
(強引用):強可達性的引用,對象保存在內存中,只有去掉強可達,對象才被回收,通常我們編寫的代碼都是Strong Ref。
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實現。
shallow heap
及retained 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都可以被釋放)。
outgoing references
與incoming references
outgoing references
:表示該對象的出節點(被該對象引用的對象)。
incoming references
:表示該對象的入節點(引用到該對象的對象)。
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三個是平級的。
在執行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
當發生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 Tree
以Dominator Tree
為視圖,可以方便的看出受當前對象“支配”的對象中哪個占用Retained Heap比較大。圖中可看出對象都在ArrayList
中,而ArrayList
下有Object
數組,數組下是User
對象。此可以知道問題出在哪里了。需要針對這個位置,查看代碼,找出導致這個數組存儲的User
過量的原因。
注:在原始堆轉儲文件的目錄下,
MAT
已經將報告的內容壓縮打包到一個zip文件,命名為“xxx_Leak_Suspects.zip”,整個報告是一個HTML格式的文件,可以用瀏覽器直接打開查看,可以方便進行報告分發、分享。
點擊Overview
頁面Actions
區域內的“Histogram視圖”或點擊工具欄的“histogram”按鈕,可以顯示直方圖列表,它以Class類的維度展示每個Class類的實例存在的個數、 占用的Shallow heap
和 Retained內存
大小,可以分別排序顯示。如下圖所示:
Shallow Heap
與Retained Heap
的概念前面已經講過。 對于java的對象,其成員基本都是引用。真正的內存都在堆上,看起來是一堆原生的byte[], char[], int[],因此如果只看對象本身的內存,數量都很小,而多數情況下,在Histogram視圖看到實例對象數量比較多的類都是一些基礎類型(通常都排在前面),如char[]、String、byte[],所以僅從這些是無法判斷出具體導致內存泄露的類或者方法的。從上圖中,可以直接看到User
對象的數量很多,有時不容易看出來的,可以使用工具欄中的group result by
可以以super class
,class loader
等排序,需要特別注意自定義的classLoader
,如下圖:
通常,如果Histogram視圖展示的數量多的實例對象不是基礎類型,而是用戶自定義的類或者有嫌疑的類,就要重點關注查看。想進一步分析的,可以右鍵,選擇使用List objects
或 Merge Shortest Paths to GC roots
等功能繼續鉆取數據。其中list objects
分別有outgoing references
及incoming references
,可以找出由這個對象出去的引用及通過哪些引用到這個對象的。Merge Shortest Paths to GC roots
可以排除掉所有不是強引用的,找到這個對象的到GC root
的引用路徑。最終目的就是找到占用內存最大對象和無法回收的對象,計算從垃圾收集器根到相關對象的路徑,從而根據這個對象路徑去檢查代碼,找出問題所在。
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
對象。
點擊工具欄的“齒輪”按鈕,可以打開Thread Overview
視圖,可以查看線程的棧幀信息,包括線程對象/線程棧信息、線程名、Shallow Heap
、Retained Heap
、類加載器、是否Daemon線程等信息。結合內存Dump分析,看到線程棧幀中的本地變量,在左下方的對象屬性區域還能看到本地變量的屬性值。如按上面的示例(shortest paths to GC root
),知道是由于線程Thread-12
是GC-root
占用內存大,在線程視圖中,就可以著重看它的屬性情況,如下圖:
由上圖可以看到此線程調用WithOOM
方法,使用了變量UserService
,而變量使用了userList
,它包含了大量的User
對象,占用retained heap
很大。
對于java應用的內存分析,需要對java應用的內存進行dump操作,生成內存快照后,使用MAT
進行分析,找出大對象,找到內存泄漏或溢出的地方,從而分析代碼,解決問題。本文對MAT
的使用場景,基本概念,安裝、使用進行了詳細介紹,大家可以自己安裝一下,寫個小示例或者拿本文中的示例,實踐一下。通過本文,希望可以幫助大家更方便,更有效率地分析內存,解決java應用的內存故障問題。
看完上述內容,你們掌握java 第三方內存分析工具MAT怎么用的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。