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

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

1、Java內存管理機制

1. Java內存分配策略

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

對應的存儲區域以下:數組

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

2. 堆與棧的區別

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

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

3. Java管理內存的機制

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

1). 釋放對象的原則:

該對象再也不被引用。oop

2). GC的工做原理:

將對象考慮爲有向圖的頂點,將引用關係考慮爲有向圖的有向邊,有向邊從引用者指向被引對象。另外,每一個線程對象能夠做爲一個圖的起始頂點,例如大多程序從 main 進程開始執行,那麼該圖就是以 main 進程爲頂點開始的一棵根樹。在有向圖中,根頂點可達的對象都是有效對象,GC將不回收這些對象。若是某個對象與這個根頂點不可達,那麼咱們認爲這個對象再也不被引用,能夠被 GC 回收。
另外,Java使用有向圖的方式進行內存管理,能夠消除引用循環的問題,例若有三個對象相互引用,但只要它們和根進程不可達,那麼GC也是能夠回收它們的。固然,除了有向圖的方式,還有一些別的內存管理技術,不一樣的內存管理技術各有優缺點,在這裏就不詳細展開了。

3). Java中的內存泄漏

若是一個對象知足如下兩個條件:post

(1)這些對象是可達的,即在有向圖中,存在通路能夠與其相連測試

(2)這些對象是無用的,即程序之後不會再使用這些對象spa

就能夠斷定爲Java中的內存泄漏,這些對象不會被GC所回收,繼續佔用着內存。

在C++中,內存泄漏的範圍更大一些。有些對象被分配了內存空間,而後卻不可達,因爲C++中沒有GC,這些內存將永遠收不回來。在Java中,這些不可達的對象都由GC負責回收。

2、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等。

3、使用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(引用樹),也大概列出了該實體類被引用的路徑。經過這些咱們就能大概能猜到是哪裏致使了內存泄漏。

相關文章
相關標籤/搜索