轉載請注明位址:http://blog.csdn.net/yincheng886337/article/details/50524890
MAT工具使用
了解相關概念
在了解MAT工具之前,我們需先對以下幾個概念有所認知:
1)強引用、弱引用、軟引用、虛引用
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