在使用Android Studio進行內存泄露分析之前,我們先回顧一下Java相關的內存管理機制,然后再講述一下內存分析工具如何使用。
一、Java內存管理機制
1. Java內存分配策略
Java 程序運行時的內存分配策略有三種:靜態分配、棧式分配和堆式分配。
對應的存儲區域如下:
- 靜態存儲區(方法區):主要存放靜態數據、全局 static 數據和常量。這塊內存在程序編譯時就已經分配好,並且在程序整個運行期間都存在。
- 棧區 :方法體內的局部變量都在棧上創建,並在方法執行結束時這些局部變量所持有的內存將會自動被釋放。
- 堆區 : 又稱動態內存分配,通常就是指在程序運行時直接 new 出來的內存。這部分內存在不使用時將會由 Java 垃圾回收器來負責回收。
2. 堆與棧的區別
棧內存:在方法體內定義的局部變量(一些基本類型的變量和對象的引用變量)都是在方法的棧內存中分配的。當在一段方法塊中定義一個變量時,Java 就會在棧中為該變量分配內存空間,當超過該變量的作用域后,分配給它的內存空間也將被釋放掉,該內存空間可以被重新使用。
堆內存:用來存放所有由 new 創建的對象(包括該對象其中的所有成員變量)和數組。在堆中分配的內存,將由 Java 垃圾回收器來自動管理。在堆中產生了一個數組或者對象后,還可以在棧中定義一個特殊的變量,這個變量的取值等於數組或者對象在堆內存中的首地址,這個特殊的變量就是我們上面說的引用變量。我們可以通過這個引用變量來訪問堆中的對象或者數組。
3. Java管理內存的機制
Java的內存管理就是對象的分配和釋放問題。內存的分配是由程序員來完成,內存的釋放由GC(垃圾回收機制)完成。GC 為了能夠正確釋放對象,必須監控每一個對象的運行狀態,包括對象的申請、引用、被引用、賦值等。這是Java程序運行較慢的原因之一。
1). 釋放對象的原則:
該對象不再被引用。
2). GC的工作原理:
3). Java中的內存泄漏
如果一個對象滿足以下兩個條件:
(1)這些對象是可達的,即在有向圖中,存在通路可以與其相連
(2)這些對象是無用的,即程序以后不會再使用這些對象
就可以判定為Java中的內存泄漏,這些對象不會被GC所回收,繼續占用着內存。
在C++中,內存泄漏的范圍更大一些。有些對象被分配了內存空間,然后卻不可達,由於C++中沒有GC,這些內存將永遠收不回來。在Java中,這些不可達的對象都由GC負責回收。
二、Android中的內存泄漏
1. 單例造成的內存泄漏
在Android開發中,常見的單例問題造成內存泄漏的場景如下:
當創建這個單例的時候,由於需要傳入一個Context,所以這個Context的生命周期的長短至關重要:
1.如果此時傳入的是 Application 的 Context,因為 Application 的生命周期就是整個應用的生命周期,所以沒有任何問題。
2.如果此時傳入的是 Activity 的 Context,當這個 Context 所對應的 Activity 退出時,由於該 Context 的引用被單例對象所持有,其生命周期等於整個應用程序的生命周期,所以當前 Activity 退出時它的內存並不會被回收,這就造成泄漏了。
當然,Application 的 context 不是萬能的,所以也不能隨便亂用,例如Dialog必須使用 Activity 的 Context。
2. 非靜態內部類創建靜態實例造成的內存泄漏
非靜態內部類默認會持有外部類的引用,而該非靜態內部類又創建了一個靜態的實例,該實例的生命周期和應用的一樣長,這就導致了該靜態實例一直會持有該Activity的引用,導致Activity的內存資源不能正常回收。
3. 匿名內部類造成的內存泄漏
匿名內部類默認也會持有外部類的引用。
如果在Activity/Fragment中使用了匿名類,並被異步線程持有,如果沒有任何措施這樣一定會導致泄漏。
例子:Handler造成的內存泄漏。

4. 資源未關閉造成的內存泄漏
對於使用了BraodcastReceiver,ContentObserver,File, Cursor,Stream,Bitmap等資源的使用,應該在Activity銷毀時及時關閉或者注銷,否則這些資源將不會被回收,造成內存泄漏。
5. 不良代碼造成的內存使用壓力
有些代碼並不造成內存泄漏,但是它們,或是對沒使用的內存沒進行有效及時的釋放,或是沒有有效的利用已有的對象而是頻繁的申請新內存。比如,Adapter里沒有復用convertView等。
三、使用Android Studio的Memory Monitor來分析內存泄漏情況

最原始的內存泄漏排查方式如下:
重復多次操作關鍵的可疑的路徑,從內存監控工具中觀察內存曲線,看是否存在不斷上升的趨勢,且退出一個界面后,程序內存遲遲不降低的話,可能就發生了嚴重的內存泄漏。
這種方式可以發現最基本,也是最明顯的內存泄漏問題,對用戶價值最大,操作難度小,性價比極高。
下面就開始用一個簡單的例子來說明一下如何排查內存泄漏。
首先,創建了一個TestActivity類,里面的測試代碼如下:
@Override protected voidprocessBiz() { mHandler = new Handler(); mHandler.postDelayed(newRunnable() { @Override public voidrun() { MLog.d("------postDelayed------"); } }, 800000L); }
運行項目,並執行以下操作:進入TestActivity,然后退出,再重新進入,如此操作幾次后,最后最終退出TestActivity。這時發現,內存持續增高,如圖所示:

這時我們可以假設,這里可能出現了內存泄漏的情況。那么,如何繼續定位到內存泄漏的地址呢?這時候就得點擊“Dump java heap”按鈕來收集具體的信息了。
下面我們就要需要使用Android Studio生成Java Heap文件來分析內存情況了。
注意,在點擊 Dump java heap 按鈕之前,一定要先點擊Initate GC按鈕強制GC,建議點擊后等待幾秒后再次點擊,嘗試多次,讓GC更加充分。然后再點擊Dump Java Heap按鈕。
這時候會生成一個Java heap文件並在新的窗口打開:
這時候,點擊右上角的“Analyzer Task”,再點擊出現的綠色按鈕,讓Android studio幫我們自動分析出有可能潛在的內存泄漏的地方:
如上圖所示,Android studio提示有3個TestActivity對象可能出現了內存泄漏。而且左邊的Reference Tree(引用樹),也大概列出了該實體類被引用的路徑。通過這些我們就能大概能猜到是哪里導致了內存泄漏。