版權(quán)聲明:本文為LooperJing原創(chuàng)文章,轉(zhuǎn)載請注明出處!

在Android性能優(yōu)化第(一)篇---基本概念中講了JAVA的四大引用,講了一下GCRoot,第二篇Memory Monitor檢測內(nèi)存泄露僅僅說了Menmery Monitor的使用,這篇博客談一下MAT來尋找內(nèi)存泄露,相對來說,Memory Monitor沒有MAT強(qiáng)大,但是在開始介紹MAT之前,上兩篇沒有說清楚的問題先說一下。
-
GC回收對可回收對象的判定
什么樣的對象是可以被回收的?
當(dāng)然是GC發(fā)現(xiàn)通過任何referencechain(引用鏈)無法訪問某個對象的時候,該對象即被回收。名詞GC Roots正是分析這一過程的起點(diǎn),例如JVM自己確保了對象的可到達(dá)性(那么JVM就是GC Roots),所以GCRoots就是這樣在內(nèi)存中保持對象可到達(dá)性的,一旦不可到達(dá),即被回收。通常GC Roots是一個在current thread(當(dāng)前線程)的call stack(調(diào)用棧)上的對象(例如方法參數(shù)和局部變量),或者是線程自身或者是system class loader(系統(tǒng)類加載器)加載的類以及native code(本地代碼)保留的活動對象。所以GC Roots是分析對象為何還存活于內(nèi)存中的利器。

上面這段話,也寫出了檢測內(nèi)存泄露的基本思想:以”GC Roots”的對象作為起始點(diǎn)向下搜索,搜索形成的路徑稱為引用鏈,當(dāng)一個對象到GC Roots沒有任何引用鏈相連(即不可達(dá)的),則該對象被判定為可以被回收的對象,反之不能被回收。也寫出了內(nèi)存泄露的原因:對象無用了,但仍然可達(dá)(未釋放),垃圾回收器無法回收。
好啦,下面開始介紹MAT,MAT工具全稱為Memory Analyzer Tool,(MAT下載:http://eclipse.org/mat/downloads.php)是一款詳細(xì)分析Java堆內(nèi)存的工具,該工具非常強(qiáng)大,為了使用該工具,我們需要hprof文件。但是該文件不能直接被MAT使用,需要進(jìn)行一步轉(zhuǎn)化,可以使用hprof-conv命令來轉(zhuǎn)化,但是AndroidStudio可以直接轉(zhuǎn)化。

然後用MAT打開我們導(dǎo)出的文件,我導(dǎo)出了兩個文件,test1.hprof和test2.hprof,其中test1.hprof是內(nèi)存未泄露時的快照,test2.hprof是內(nèi)存已經(jīng)泄露的快照。我們用MAT的Histogram(直方圖)和Dominator Tree (支配樹)來分析內(nèi)存情況。Histogram可以列出內(nèi)存中每個對象的名字、數(shù)量以及大小。Dominator Tree會將所有內(nèi)存中的對象按大小進(jìn)行排序,并且我們可以分析對象之間的引用結(jié)構(gòu)。

一、 Histogram(直方圖)
可列出每一個類的實(shí)例數(shù)。支持正則表達(dá)式查找,也可以計(jì)算出該類所有對象的retained size。默認(rèn)是通過class(group by class)分類展示的。

這就是test2.hprof的直方圖?,F(xiàn)在要說兩個名詞解釋。Shallow Heap/Retained Heap
- Shallow Heap
Shallow size就是對象本身占用內(nèi)存的大小,不包含其引用的對象內(nèi)存,實(shí)際分析中作用不大。在堆上,看起來是一堆原生的byte[], char[], int[],對象本身的內(nèi)存都很小。所以我們可以看到以Shallow Heap進(jìn)行排序的Histogram圖中,排在第一位第二位的是byte,char - Retained Heap
Retained size是該對象自己的shallow size,加上從該對象能直接或間接訪問到對象的shallow size之和。換句話說,retained size是該對象被GC之后所能回收到內(nèi)存的總和。RetainedHeap可以更精確的反映一個對象實(shí)際占用的大小(因?yàn)槿绻搶ο筢尫?,retained heap都可以被釋放)。
Histogram中是可以顯示對象的數(shù)量的,那么比如說我們現(xiàn)在懷疑MainActivity中有可能存在內(nèi)存泄漏,就可以在第一行的正則表達(dá)式框中搜索“MainActivity”,如下所示:

可以看到MainActivity的數(shù)量是2,這不正常,解決這個,就需要查看MainActivity被誰引用了,不能被釋放。我們右鍵選擇exclude all phantom/weak/soft etc.references, 意思是查看排除虛引用/弱引用/軟引用等的引用鏈 (這些引用最終都能夠被GC干掉,所以排除)

還有其他菜單供選擇
- List objects with (以Dominator Tree的方式查看)
- incoming references 引用到該對象的對象
- outcoming references 被該對象引用的對象
- Show objects by class (以class的方式查看)
- incoming references 引用到該對象的對象
- outcoming references 被該對象引用的對象
當(dāng)你按上面操作之后,兇手就出現(xiàn)了,原來是UserManger的實(shí)例。

還有可以通過包名來查看Histogram。

還有更強(qiáng)大的,通過OQL語句查詢,有點(diǎn)像寫SQL語句。

是不是分分鐘查出MainActivity有兩個對象。哈哈!
比如:查找size=0并且未使用過的ArrayList
select * from java.util.ArrayList where size=0 and modCount=0
這個地方可以多研究研究。
二、Dominator Tree(支配樹)
Dominator Tree是對象之間dominator關(guān)系樹。如果從GC Root到達(dá)Y的的所有path都經(jīng)過X,那么我們稱X dominates Y,或者X是Y的Dominator Dominator Tree由系統(tǒng)中復(fù)雜的對象圖計(jì)算而來。從MAT的dominator tree中可以看到占用內(nèi)存最大的對象以及每個對象的dominator。 我們也可以右鍵選擇Immediate Dominator”來查看某個對象的dominator。它可以將所有對象按照Heap大小排序顯示, 這樣大內(nèi)存對象就是排在前幾名的,我們可以搜索大內(nèi)存對象通向GC Roots的路徑,因?yàn)閮?nèi)存占用越高的對象越值得懷疑,使用方法跟Histogram(直方圖)差不多,在這里我就不做過多的介紹了。
三、內(nèi)存快照對比
我們可以將test1.hprof的直方圖與test2.hprof的直方圖對比來看,其中Object#0是test1.hprof的,Object#1是test2.hprof的,通過比較看哪一些對象的大小相差過大。

其他資料:
官方的幫助文檔
http://wiki.eclipse.org/index.php/MemoryAnalyzer
使用 Eclipse Memory Analyzer 進(jìn)行堆轉(zhuǎn)儲文件分析
http://www.vogella.de/articles/EclipseMemoryAnalyser/article.html
MAT使用教程
http://www.vogella.de/articles/EclipseMemoryAnalyser/article.html