基本步驟:
1,使用eclipse 自帶的 DDMS 工具分析各線程的內存使用情況,如下圖所示
Heap視圖界面會定時刷新,在對應用的不斷的操作過程中就可以看到內存使用的變化。
怎樣判斷當前進程是否有內存泄漏呢?
這裏需要注意一個值:VM Heap頁面中部有一個data object選項,即數據對象,也就是我們的程序中大量存在的類類型的對象。
在data object一行中有一列是“Total Size”,其值就是當前進程中所有Java數據對象的內存總量,一般情況下,這個值的大小決定了是否會有內存泄漏。如上圖中選中行所示。
可以據此判斷內存有泄漏:
1) 不斷的操作當前應用,或者重複某一動作,注意觀察data object的Total Size值。
2) 正常情況下Total Size值都會穩定在一個有限的範圍內,也就是說如果程序中的的代碼邏輯良好,
沒有創建的對象不被GC機制正常回收的情況,即便 我們不斷的操作生成很多對象,而在虛擬機不斷的進行垃圾回收的過程中,這些對象都被正常回收了,內存使用量會保持在一個比較穩定的水平。
3) 如果代碼中存在對象引用沒有釋放的情況,則data object的Total Size值在每次GC後不會有明顯的回落,隨着操作次數的增多Total Size的值會越來越大。
正常情況下,一個虛擬機的進程的內存在64M, 如果內存泄漏會發現 Heap Size 在不斷的逼近 64M, 一旦達到這個值時,就會出現退出應用等情況。
發生內存泄露,Total Size的值越來越大時,按下“Dump HPROF file”按鈕,這個時候會提示設置hprof文件的保存路徑。保存後,可以對比log來分析是哪些操作造成了內存泄漏。
2,點擊 按鈕,導出 hprof 文件,使用MAT 工具進行分析。具體分析步驟和過程詳見下面鏈接
http://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-ma/index.html
eclipse安裝地址:http://download.eclipse.org/mat/1.3/update-site/
3,打開 MAT 工具,File-->Open Heap Dump... 選擇你剛剛保存的 hprof 文件打開
此時,可能會彈出一個錯誤,如下圖所示:
哦,不要以爲是 MAT 工具版本不對,其實是 android 的 hprof 文件在這裏需要進行轉換一下格式纔可以使用 MAT 打開,不知道 谷歌在這裏
搗了什麼鬼,難道是優化?
使用 android sdk 目錄下的 tools 中一個工具進行轉化一下
4,使用AndrodiSDK/tools/hprof-conv轉化hprof文件,
首先,要通過控制檯進入到你的 android sdk tools 目錄下
例如 hprof-conv input.hprof out.hprof
再使用MAT工具打開轉換後的 hprof 文件,就能看到完整的內存使用分析報告了。如下所示是 MAT 分析內存使用的主界面:
在其中懷疑的地方,點擊 Details 就可以看到具體的內存使用情況了。
tip1:
有一種比較好的方法是,在內存泄漏開始時抓取一個 hprof 文件,在內存泄漏很厲害時,app 瀕臨崩潰時再抓取一個hprof 文件。
對比看這兩個圖,就很容易看出來上面的餅圖中哪一塊存在內存泄漏。
有的時候能直接看出來多了一塊。那麼我們就從那一塊入手進行分析。比較快能得到結果。
tip2:
看 dominator_tree,可以從列表中 data_object 最多的幾項數據入手分析,如下文件所示(136,80對應的兩項)
我這邊曾經就因爲在 onStart 中添加了一個 PhoneStateListener 的監聽,而在 onStop 中未設置爲空,導致內存泄漏。
這裏引用一點別人總結的實例:
原因1:
BraodcastReceiver,ContentObserver,FileObserver,Cursor在Activity onDeatory或者某類聲明週期結束之後一定要unregister或者close掉,否則這個Activity類會被system強引用,不會被內存回收。
原因2:
不要直接對Activity進行直接引用作爲成員變量,如果不得不這麼做,請用private WeakReference mActivity來做,相同的,對於Service等其他有自己聲明週期的對象來說,直接引用都需要謹慎考慮是否會存在內存泄露的可能。()
private static class MyHandler extends Handler { private WeakReference<GeneralSettings> mStatus; public MyHandler(GeneralSettings activity) { mStatus = new WeakReference<GeneralSettings>(activity); } @Override public void handleMessage(Message msg) { GeneralSettings status = mStatus.get(); if (status == null) { return; } switch (msg.what) { case EVENT_UPDATE_STATS: status.updateTimes(); sendEmptyMessageDelayed(EVENT_UPDATE_STATS, 1000); break; } } }
原因3:
對 Context 保持了一個長生命週期的引用。
private static Drawable sBackground;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
sBackground的生命週期比Activity要長,label引用到context,sBackground又把label設爲內部成員變量,所以sBackground引用到了context,導致activity結束的時候context還是不能釋放,從而引發內存泄露。(不甚理解,還要仔細研究一下)
最後,作者給了一點總結(有些地方還是不太懂……)
總結:
1. 對activity的引用應該控制在activity的生命週期之內;
2. 如果不能就考慮使用getApplicationContext或者getApplication;
3. 儘量不要在靜態變量或者靜態內部類中使用非靜態外部成員變量(包括context),即使要使用,也要考慮適時把外部成員變量置空(如上例可以通過把sBackground的callback置空來解決內存泄露的問題);也可以在內部類中使用弱引用來引用外部類的變量;
4. 做到在onDestroy中釋放資源,如清空對圖片等資源有直接引用或者間接引用的數組(使用array.clear();array = null);
安卓開發論壇 http://www.eoeandroid.com/