轉載請註明地址:http://blog.csdn.net/yincheng886337/article/details/50524890
MAT工具使用
理解相關概念
在瞭解MAT工具之前,我們需先對以下幾個概念有所認知:
2)Shallow Size、Retained Size、Heap Size和Allocated
MAT工具實戰
看完1)、2)兩篇博客,相信大家此時對幾個概念已具備了一定認知,下面就進入正題MAT工具的使用,說到MAT工具(Memory Analyzer Tool),首先是工具的獲取與安裝。
MAT工具獲取路徑:http://download.eclipse.org/mat/1.5/update-site/
MAT安裝步驟詳情請自行百度
安裝好工具後,就需要去獲取hprof文件,如果獨立版的MAT,需採用hprof-conv <源文件> <目標文件> 命名進行轉換。
下面以Android Studio dump操作示例:
步驟1:
圖1.抓取hprof文件
步驟2:
圖2.轉換hprof文件
抓好hprof文件後,採用MAT打開hprof文件後,看到的界面如下圖:
圖3.MAT的overview圖
有了MAT和hprof文件,且功能正常,馬上進入MAT分析內存階段。採用MAT進行內存分析主要分析以下幾點:
1.程序是否內存泄露;
2.程序中大對象的佔用是否合理;
3.程序中部分對象產生內存是否可以優化;
有了分析的目標,我們就開始朝着目標開始分析:
方法1:根據Leak Suspects快速查看泄露的可疑點
Step1:進入Leak Suspects頁面(如圖4)
圖4.Leak Suspect圖
Step2:進入Leak Suspects的詳情頁面(如圖5)
圖5.Leak Suspect詳情頁面
Step3:查看泄露可疑對象(如圖6)
圖6.泄露可疑對象
Step4:查看泄露可疑對象被誰引用(如圖7)
圖7.泄露可疑對象引用關係
Step5:可疑對象被確認爲一張圖片後,就查看圖片的相關屬性並做相應記錄(如圖8)
圖8.查看可疑對象屬性
Step6:將可疑對象保存至具體位置,便於查看圖片真相,注意文件保存一定要以.data爲後綴(如圖9)
圖9.保存圖片
Step7:採用GIMP軟件打開剛保存的文件,修改圖片類型以及寬和高(寬高和Step5中屬性一致)(如圖10)
圖10.GIMP打開圖片
至此我們就可以確定該可疑對象是哪張圖片,然後找到相應的代碼,查看爲何該圖片沒有釋放,爲何它這麼大,是否可以優化?
方法1優化建議:
1.如果是切圖問題或是矢量圖,可以從圖片上進行優化,如製作成.9圖抑或自己用xml做drawable圖;
2.如果使用完沒有釋放,抑或還沒有顯示就已經加載,可以考慮採用ViewStub來加載,但ViewStub不能與merge共同使用,否則會報錯。
方法2:利用Top Consumers或Dominator Tree查看大對象(如圖11-12)
圖11. 利用Top Consumers查看大對象
圖12 利用Dominator Tree查看大對象
由於此種方法比較直觀,在此就不贅述,如有不懂請自行查閱其他文獻
方法2優化建議:
當面對大對象時,如果是集合類存儲的對象,可以考慮使用下Android提供的ArrayMap以及SparseArray來替換HashMap;
方法3:利用Histogram和Dominator Tree分析內存泄露
在分析內存泄露時,必須要掌握粒度,所謂粒度就是你此刻dump的hprof文件究竟是分析誰的泄露,如果你在開始前心中沒有個目標,最後取出來的hprof也分析不出什麼原因。粒度越小,對你分析問題也就越有利,當你把一個個小粒度問題解決後,整個App的泄露就迎刃而解了。也許這麼說,大家心中有點迷糊。下面就舉例來說吧:
假如現在有個項目包含Module幾十個,每個Module包含的Activity數以百計,現在讓你分析它是否內存泄露,如果你只是胡亂抓個hprof根本分析不出什麼。假如你就針對某個Activity分析這樣問題就簡單多了。比如你現在分析ActivityA的內存泄露問題,你可以參考如下步驟:
Step1:進入ActivityA之前,你先dump個hprof文件HprofA;
Step2:進入ActivityA操作一會,再退出ActivityA後dump個hprof文件HprofB;
Step3:採用Histogram和Dominator Tree對比分析這兩個Hprof文件,即可得出ActivityA是否泄露
現在以分析TestActivity爲例,按上述步驟實戰分析,先抓取進入TestActivity前後的hprof文件,按如下步驟對比兩個hprof的異同(如圖13-14):
圖13 選擇所需比較的hprof
圖14 比較兩個hprof
正如圖14所示,易知在執行進出TestActivity後,多出了個TestActivity對象,按理論上來說在進入Activity後會創建個Activity,但是按Back鍵返回後這個Activity就會被銷燬進而從Task棧上被移除,也就是說這個操作前後不應該會多出個Activity,因此可以斷定TestActivity存在泄漏。
TestActivity存在泄漏,那我們應該怎麼解決呢?因此我們就需要找到爲何泄漏,爲什麼本該銷燬的Activity卻沒有被銷燬?如知真相如何,請看下圖15-16
圖15 獲取TestActivity的Reference chain
圖16TestActivity的引用關係
從圖16易知TestActivity沒有被釋放就是因爲GC Root(TestActivity$1)引用着TestActivity,到此原因也一目瞭然。找到了只是開始,解決纔是關鍵。這時讓我們查看下TestActivity代碼:
public class TestActivity extends Activity {
private static final Object mLock = new Object();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DebugUtil.StrictModeDebug();
setContentView(R.layout.test_main);
new Thread(){//匿名線程
public void run() {
synchronized (mLock) {
try {
mLock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
}
從代碼上可以發現TestActivity裏存在個匿名線程,且一直處於等待狀態,直到退出TestActivity仍未被喚醒,進而導致該線程就一直沒有結束,它所持有的TestActivity也就無法被釋放了(可能大家聽到此處會很疑惑,線程沒有結束可以理解,但是它並沒有持有TestActivity呀?我只能說是隱含this,如還不明白,請自行參閱java內部類相關內容),如要解決此泄露,只需在Activity的onDestory裏將線程喚醒讓其可以正常結束就OK了。
方法3優化建議:
1.使用線程時,一定要確保線程在週期性對象(如Activity)銷燬時能正常結束,如能正常結束,但是Activity銷燬後還需執行一段時間,也可能造成泄露,此時可採用WeakReference方法來解決,另外在使用Handler的時候,如存在Delay操作,也可以採用WeakReference;
2.使用Handler + HandlerThread時,記住在週期性對象銷燬時調用looper.quit()方法;
3.建議少使用匿名類或內部類,可考慮使用嵌套類(帶static那種類),減少對週期性對象的隱性持有;
至此,MAT使用也告一段落,但其功能遠不止於此,如需瞭解更多可參閱如下網址:
1.http://ju.outofmemory.cn/entry/129445
2.http://developer.android.com/intl/zh-cn/training/improving-layouts/smooth-scrolling.html#ViewHolder
3.http://help.eclipse.org/luna/index.jsp?topic=/org.eclipse.mat.ui.help/welcome.html