分析內存泄漏的工具與方法

1.adb shell && Memory Usage

以通過命令 adb shell dumpsys meminfo [package name] 來將指定 package name 的內存信息打印出來,這種模式可以非常直觀地看到 Activity 未釋放導致的內存泄漏:

或者也可以通過 Android studio 的 Memory Usage 功能進行查看,最後的結果是一樣的:

2.利用finalise方法

上面介紹finalise方法時有說過:當GC準備回收一個Java Object(所有Java對象都是Object的子類)的時候,GC會調用這個Object的finalize方法。也就意味着如果某個對象比如activity泄漏的話,那麼在退出這個activity時,GC不會調用這個activity的finalize方法,我們可以利用這一點,重寫activity的finalise方法,在裏面打印一段日誌,如下所示:

我們balabala打開很多activity,再返回到主頁,通過IDE手動觸發幾次GC操作,如果這時某個activity沒有打印finalise方法裏的日誌時,說明這個activity就發生內存泄漏了。怎麼樣?是不是很簡單粗暴!

需要注意一點的是,由於重寫了finalize方法的對象要第二次GC才能會真正被GC回收,這也是一種泄漏,所以檢查完內存泄漏後,別忘了刪除重寫的finalise方法。

 

3.Allocation Tracker

Android studio 還自帶一個 Allocation Tracker 工具,功能和 DDMS 中的基本差不多,這個工具可以監控一段時間之內的內存分配:

在內存圖中點擊途中標紅的部分,啓動追蹤,再次點擊就是停止追蹤,隨後自動生成一個 .alloc 文件,這個文件就記錄了這次追蹤到的所有數據,然後會在右上角打開一個數據面板:

 

4.Android Memory Monitor

Memory Monitor 是 Android Studio 自帶的一個監控內存使用狀態的工具,入口如下所示:

// 待補充圖片

在 Android Monitor 點開之後 logcat 的右側就是 Monitor 工具,其中可以檢測內存、CPU、網絡等內容,我們這裏只用到了 Memory Monitor 功能,點擊紅色箭頭所指的區域,就會 dump 此時此刻的 Memory 信息,並且生成一個 .hprof 文件,dump 完成之後會自動打開這個文件的顯示界面,如果沒有打開,可以通過點擊最左側的 Capture 界面或者 Tool Window 裏面的 Capture 進入 dump 的 .hprof 文件列表:

首先左上角的下拉框,可以選擇 App Heap、Image Heap 和 Zygote Heap,對應的就是上篇博客講到的 Allocation Space,Image Space 和 Zygote Space,我們這裏選擇 Allocation Space,然後第二個選擇 PackageTreeView 這一項,展開之後就能看見一個樹形結構了,然後繼續展開我們應用包名的對應對象,就可以很清晰的看到有多少個 Activity 對象了,上面那兩欄展示的信息按照從左到右的順序,定義如下所示:

Column Description
Class Name 佔有這塊內存的類名
Total Count 未被處理的數量
Heap Count 在上面選擇的指定 heap 中的數量
Sizeof 這個對象的大小,如果在變化中,就顯示 0
Shallow Size 在當前這個 heap 中的所有該對象的總數
Retained Size 這個類的所有對象佔有的總內存大小
Instance 這個類的指定對象
Reference Tree 指向這個選中對象的引用,還有指向這個引用的引用
Depth 從 GC Root 到該對象的引用鏈路的最短步數
Shallow Size 這個引用的大小
Dominating Size 這個引用佔有的內存大小

然後可以點擊展開右側的 Analyzer Tasks 項,勾選上需要檢測的任務,然後系統就會給你分析出結果:

// 待補充圖片

從分析的結果可以看到泄漏的 Activity 有兩個,非常直觀,然後點開其中一個,觀察下面的 ReferenceTree 選項:

// 待補充圖片

可以看到 Thread 對象持有了 SecondActivity 對象的引用,也就是 GC Root 持有了該 Activity 的引用,導致這個 Activity 無法回收,問題的根源我們就發現了,接下來去處理它就好了。

 

5.MAT

MAT(Memory Analyzer Tools)是一個 Eclipse 插件,它是一個快速、功能豐富的 Java heap 分析工具,它可以幫助我們查找內存泄漏和減少內存消耗,MAT 插件的下載地址:Eclipse Memory Analyzer Open Source Project,上面通過 Android studio 生成的 .hprof 文件因爲格式稍有不同,所以需要經過一個簡單的轉換,然後就可以通過 MAT 去打開了:   通過 MAT 去打開轉換之後的這個文件:

用的最多的就是 Histogram 功能,點擊 Actions 下的 Histogram 項就可以得到 Histogram 結果:

我們可以在左上角寫入一個正則表達式,然後就可以對所有的 Class Name 進行篩選了,很方便,頂欄展示的信息 “Objects” 代表該類名對象的數量,剩下的 “Shallow Heap” 和 “Retained Heap” 則和 Android Memory Monitor 類似。咱們接着點擊 SecondActivity,然後右鍵:

在彈出來的菜單中選擇 List objects->with incoming references 將該類的實例全部列出來:

通過這個列表我們可以看到 SecondActivity@0x12faa900 這個對象被一個 this$00x12c65140 的匿名內部類對象持有,然後展開這一項,發現這個對象是一個 handler 對象:

快速定位找到這個對象沒有被釋放的原因,可以右鍵 Path to GC Roots->exclude all phantom/weak/soft etc. references 來顯示出這個對象到 GC Root 的引用鏈,因爲強引用纔會導致對象無法釋放,所以這裏我們要排除其他三種引用:

這麼處理之後的結果就很明顯了:

一個非常明顯的強引用持有鏈,GC Root 我們前面的博客中說到包含了線程,所以這裏的 Thread 對象 GC Root 持有了 SecondActivity 的引用,導致該 Activity 無法被釋放。

MAT 還有一個功能就是能夠對比兩個 .hprof 文件,將兩個文件都添加到 Compare Basket 裏面:    添加進去之後點擊右上角的 ! 按鈕,然後就會生成兩個文件的對比:

同樣適用正則表達式將需要的類篩選出來:

結果也很明顯,退出 Activity 之後該 Activity 對象未被回收,仍然在內存中,或者可以調整對比選項讓對比結果更加明顯:

也可以對比兩個對象集合,方法與此類似,都是將兩個 Dump 結果中的對象集合添加到 Compare Basket 中去對比,找出差異後用 Histogram 查詢的方法找出 GC Root,定位到具體的某個對象上。

 

6.LeakCanary

LeakCanary可能是上面幾個工具中最好用的了,用法如下:

在build.gradle文件中添加如下依賴:

 dependencies {
   debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
   releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
   testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
 }

然後在application中添加如下代碼:

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    LeakCanary.install(this);
    // Normal app init code...
  }
}

OK,配置完成!噼裏啪啦多點開幾個頁面,看看LeakCanary是否已經全都暴露出來了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章