Android記憶體洩漏終極解決篇(上)

Android記憶體洩漏終極解決篇(上)

一、概述
在Android的開發中,經常聽到“記憶體洩漏”這個詞。“記憶體洩漏”就是一個物件已經不需要再使用了,但是因為其它的物件持有該物件的引用,導致它的記憶體不能被回收。“記憶體洩漏”的慢慢積累,最終會導致OOM的發生,千里之堤,毀於蟻穴。所以在寫程式碼的過程中,應該要注意規避會導致“記憶體洩漏”的程式碼寫法,提高軟體的健壯性。
本文將從發現問題、解決問題、總結問題的三個角度出發,循序漸進,徹底解決“記憶體洩漏”的問題。

二、記憶體洩漏的檢查工具Heap

工慾善其事必先利其器,要檢測“記憶體洩漏”的發生,需要藉助DDMS中的Heap工具及MAT工具,Heap工具用於大致分析是否存在“記憶體洩漏”,而MAT工具則用於分析“記憶體洩漏”發生在哪裡。

Heap工具的使用介紹

這裡寫圖片描述

具體操作

1.在Devices裝置列表中,找到你所在的裝置,點選你想要監控的程序。
2.點選“Update Heap”按鈕更新堆記憶體的情況。
3.點選“Heap”檢視,檢視記憶體的情況。
4.每次在Activity的退出和進入的時候點選“Cause GC”,手動呼叫GC釋放應用的記憶體。
5.觀察data oject那一行,每一次點選“Casue GC”的時候,觀察Total Size的值,如果該值不斷增加,則說明該應用程式存在“記憶體洩漏”。

我們先模擬一下記憶體洩漏,然後通過Heap工具來判斷一下是否存在記憶體洩漏。
上一段存在記憶體洩漏的程式碼:


public class LeakAty extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.aty_leak);
testLeak();
}
/**
* 測試記憶體洩漏的程式碼
*/
private void testLeak() {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}

上述的程式碼存在記憶體洩漏,new Runnable(){}是一個非靜態的匿名內部類,所以它會強引用建立它的外圍物件LeakAty,我們來測試一下記憶體洩漏的過程,開啟手機的方向旋轉功能,不斷地旋轉手機,讓LeakAty不斷地建立新的例項。理論上如果不存在上述洩漏的程式碼,之前的Activity會在onDestory之後被回收記憶體。而一旦存在上述洩漏的程式碼,新建立的Ruannale例項會一直處於執行狀態,它不會被回收,而它強引用的LeakAty當然也不會被回收,所以在螢幕不斷旋轉,之前建立的LeakAty就不會被釋放,會導致旋轉n次,記憶體中就存在n 1個的LeakAty例項。
Heap工具第一次按下Cause GC按鈕的截圖:

這裡寫圖片描述 

上圖的data object的Total Size的大小為1.031M。經過多次的旋轉螢幕之後,我們再看一下截圖

這裡寫圖片描述 

Total Size變成了2.059M,從1.031M到2.059M,每次呼叫GC的過程中data object的總大小沒有回落,所以可以證實上面的程式碼確實是存在記憶體洩漏的問題,那麼洩漏發生在哪裡?答案可以通過MAT工具來分析得到。

三、記憶體洩漏的分析工具MAT

要通過MAT分析,需要提供一個.hprof檔案。我們可以通過”Dump HPROF file”按鈕轉存當前的堆記憶體資訊。我們將其儲存為1.hprof。

這裡寫圖片描述

匯出的1.hprof的格式需要通過..\sdk\tools\目錄下的hprof-conv.exe工具進行轉換才能被MAT成功匯入,我們將其轉換成out1.hprof
這裡寫圖片描述

將out1.hprof匯入到MAT工具中,File->Open Heap Dump…

這裡寫圖片描述

點選左邊的標籤Overview,Actions->Histogram

這裡寫圖片描述

在Histogram介面中,因為我們想要知道Activity是否洩漏了,所以輸入關鍵詞Activity,然後按下回車鍵。

這裡寫圖片描述

之後便可以得到Activity的相關的搜尋結果,下圖的搜尋結果中Activity的例項有7個。點選選中下圖示紅色框框的地方,右鍵->Merge Shortest Paths to GC Roots->exclude all phantom/weak/soft etc. references。排除虛引用、弱引用、軟引用的例項,剩下的都是強引用例項。

這裡寫圖片描述

從過濾出來的強引用的列表中,我們可以看到這七個例項都是被Thread所引用了。所以證實上面的程式碼確實存在記憶體洩漏。

這裡寫圖片描述

四、本文總結

記憶體洩漏檢測可以使用Heap工具,記憶體分析可以使用MAT工具。本文的案例中提到了一種記憶體洩漏的情況,就是非靜態內部類的物件會強引用其外圍物件,一旦這個非靜態內部類的例項沒有釋放,它的外圍物件也不會釋放,所以就會造成記憶體洩漏。下篇將具體探討一下,在Android的開發過程中,哪些寫法容易造成記憶體洩漏,該如何解決?請閱讀Android記憶體洩漏終極解決篇(下)。