Android Studio 使用Memory Monitor進行內存泄露分析


在使用Android Studio進行內存泄露分析之前,我們先回顧一下Java相關的內存管理機制,然后再講述一下內存分析工具如何使用。

一、Java內存管理機制

1. Java內存分配策略

Java 程序運行時的內存分配策略有三種:靜態分配、棧式分配和堆式分配。

對應的存儲區域如下:

  • 靜態存儲區(方法區):主要存放靜態數據、全局 static 數據和常量。這塊內存在程序編譯時就已經分配好,並且在程序整個運行期間都存在。
  • 棧區 :方法體內的局部變量都在棧上創建,並在方法執行結束時這些局部變量所持有的內存將會自動被釋放。
  • 堆區 : 又稱動態內存分配,通常就是指在程序運行時直接 new 出來的內存。這部分內存在不使用時將會由 Java 垃圾回收器來負責回收。

2. 堆與棧的區別

棧內存:在方法體內定義的局部變量(一些基本類型的變量和對象的引用變量)都是在方法的棧內存中分配的。當在一段方法塊中定義一個變量時,Java 就會在棧中為該變量分配內存空間,當超過該變量的作用域后,分配給它的內存空間也將被釋放掉,該內存空間可以被重新使用。

堆內存:用來存放所有由 new 創建的對象(包括該對象其中的所有成員變量)和數組。在堆中分配的內存,將由 Java 垃圾回收器來自動管理。在堆中產生了一個數組或者對象后,還可以在棧中定義一個特殊的變量,這個變量的取值等於數組或者對象在堆內存中的首地址,這個特殊的變量就是我們上面說的引用變量。我們可以通過這個引用變量來訪問堆中的對象或者數組。

3. Java管理內存的機制

Java的內存管理就是對象的分配和釋放問題。內存的分配是由程序員來完成,內存的釋放由GC(垃圾回收機制)完成。GC 為了能夠正確釋放對象,必須監控每一個對象的運行狀態,包括對象的申請、引用、被引用、賦值等。這是Java程序運行較慢的原因之一。

1). 釋放對象的原則:

該對象不再被引用。

2). GC的工作原理:

將對象考慮為有向圖的頂點,將引用關系考慮為有向圖的有向邊,有向邊從引用者指向被引對象。另外,每個線程對象可以作為一個圖的起始頂點,例如大多程序從 main 進程開始執行,那么該圖就是以 main 進程為頂點開始的一棵根樹。在有向圖中,根頂點可達的對象都是有效對象,GC將不回收這些對象。如果某個對象與這個根頂點不可達,那么我們認為這個對象不再被引用,可以被 GC 回收。
另外,Java使用有向圖的方式進行內存管理,可以消除引用循環的問題,例如有三個對象相互引用,但只要它們和根進程不可達,那么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造成的內存泄漏。

修復方法:在 Activity 中避免使用非靜態內部類或匿名內部類,比如將 Handler 聲明為靜態的,則其存活期跟 Activity 的生命周期就無關了。如果需要用到Activity,就通過弱引用的方式引入 Activity,避免直接將 Activity 作為 context 傳進去。另外, Looper 線程的消息隊列中還是可能會有待處理的消息,所以我們在 Activity 的 Destroy 時或者 Stop 時應該移除消息隊列 MessageQueue 中的消息。
代碼示例如圖:

4. 資源未關閉造成的內存泄漏

對於使用了BraodcastReceiver,ContentObserver,File, Cursor,Stream,Bitmap等資源的使用,應該在Activity銷毀時及時關閉或者注銷,否則這些資源將不會被回收,造成內存泄漏。

5. 不良代碼造成的內存使用壓力

有些代碼並不造成內存泄漏,但是它們,或是對沒使用的內存沒進行有效及時的釋放,或是沒有有效的利用已有的對象而是頻繁的申請新內存。比如,Adapter里沒有復用convertView等。

三、使用Android Studio的Memory Monitor來分析內存泄漏情況

先看一下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(引用樹),也大概列出了該實體類被引用的路徑。通過這些我們就能大概能猜到是哪里導致了內存泄漏。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM