天天看點

記憶體洩露分析之MAT工具使用MAT工具使用

轉載請注明位址: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:

記憶體洩露分析之MAT工具使用MAT工具使用

圖1.抓取hprof檔案

步驟2:

記憶體洩露分析之MAT工具使用MAT工具使用

圖2.轉換hprof檔案

抓好hprof檔案後,采用MAT打開hprof檔案後,看到的界面如下圖:

記憶體洩露分析之MAT工具使用MAT工具使用

圖3.MAT的overview圖

有了MAT和hprof檔案,且功能正常,馬上進入MAT分析記憶體階段。采用MAT進行記憶體分析主要分析以下幾點:

1.程式是否記憶體洩露;

2.程式中大對象的占用是否合理;

3.程式中部分對象産生記憶體是否可以優化;

有了分析的目标,我們就開始朝着目标開始分析:

方法1:根據Leak Suspects快速檢視洩露的可疑點

Step1:進入Leak Suspects頁面(如圖4)

記憶體洩露分析之MAT工具使用MAT工具使用

圖4.Leak Suspect圖

Step2:進入Leak Suspects的詳情頁面(如圖5)

記憶體洩露分析之MAT工具使用MAT工具使用

圖5.Leak Suspect詳情頁面

Step3:檢視洩露可疑對象(如圖6)

記憶體洩露分析之MAT工具使用MAT工具使用

圖6.洩露可疑對象

Step4:檢視洩露可疑對象被誰引用(如圖7)

記憶體洩露分析之MAT工具使用MAT工具使用

 圖7.洩露可疑對象引用關系

Step5:可疑對象被确認為一張圖檔後,就檢視圖檔的相關屬性并做相應記錄(如圖8)

記憶體洩露分析之MAT工具使用MAT工具使用

圖8.檢視可疑對象屬性 

Step6:将可疑對象儲存至具體位置,便于檢視圖檔真相,注意檔案儲存一定要以.data為字尾(如圖9)

記憶體洩露分析之MAT工具使用MAT工具使用

圖9.儲存圖檔 

Step7:采用GIMP軟體打開剛儲存的檔案,修改圖檔類型以及寬和高(寬高和Step5中屬性一緻)(如圖10)

記憶體洩露分析之MAT工具使用MAT工具使用

圖10.GIMP打開圖檔

至此我們就可以确定該可疑對象是哪張圖檔,然後找到相應的代碼,檢視為何該圖檔沒有釋放,為何它這麼大,是否可以優化?

方法1優化建議:

1.如果是切圖問題或是矢量圖,可以從圖檔上進行優化,如制作成.9圖抑或自己用xml做drawable圖;

2.如果使用完沒有釋放,抑或還沒有顯示就已經加載,可以考慮采用ViewStub來加載,但ViewStub不能與merge共同使用,否則會報錯。

方法2:利用Top Consumers或Dominator Tree檢視大對象(如圖11-12)

記憶體洩露分析之MAT工具使用MAT工具使用

 圖11. 利用Top Consumers檢視大對象 

記憶體洩露分析之MAT工具使用MAT工具使用

圖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):

記憶體洩露分析之MAT工具使用MAT工具使用

圖13 選擇所需比較的hprof

記憶體洩露分析之MAT工具使用MAT工具使用

圖14 比較兩個hprof

正如圖14所示,易知在執行進出TestActivity後,多出了個TestActivity對象,按理論上來說在進入Activity後會建立個Activity,但是按Back鍵傳回後這個Activity就會被銷毀進而從Task棧上被移除,也就是說這個操作前後不應該會多出個Activity,是以可以斷定TestActivity存在洩漏。

TestActivity存在洩漏,那我們應該怎麼解決呢?是以我們就需要找到為何洩漏,為什麼本該銷毀的Activity卻沒有被銷毀?如知真相如何,請看下圖15-16

記憶體洩露分析之MAT工具使用MAT工具使用

圖15 擷取TestActivity的Reference chain

記憶體洩露分析之MAT工具使用MAT工具使用

圖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