Android記憶體優化(五)詳解記憶體分析工具MAT

劉望舒發表於2017-08-24

相關文章
Android效能優化系列
Java虛擬機器系列

前言

在這個系列的前四篇文章中,我分別介紹了DVM、ART、記憶體洩漏和記憶體檢測工具的相關知識點,這一篇我們通過一個小例子,來學習如何使用記憶體分析工具MAT。

1.概述

在進行記憶體分析時,我們可以使用Memory Monitor和Heap Dump來觀察記憶體的使用情況、使用Allocation Tracker來跟蹤記憶體分配的情況,也可以通過這些工具來找到疑似發生記憶體洩漏的位置。但是如果想要深入的進行分析並確定記憶體洩漏,就要分析
疑似發生記憶體洩漏時所生成堆儲存檔案。堆儲存檔案可以使用DDMS或者Memory Monitor來生成,輸出的檔案格式為hpof,而MAT就是來分析堆儲存檔案的。
MAT,全稱為Memory Analysis Tool,是對記憶體進行詳細分析的工具,它是Eclipse的外掛,如果用Android Studio進行開發則需要單獨下載它,下載地址為:eclipse.org/mat/,這篇文章MA…

2.生成hpof檔案

2.1 準備記憶體洩漏程式碼

我們需要準備一段發生記憶體洩漏程式碼,如下所示。

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LeakThread leakThread = new LeakThread();
        leakThread.start();
    }

    class LeakThread extends Thread {
        @Override
        public void run() {
            try {
                Thread.sleep(60 * 60 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}複製程式碼

上面的程式碼是很典型的記憶體洩漏的例子,原因就是非靜態內部類LeakThread持有外部類MainActivity的引用,LeakThread中做了耗時操作,導致MainActivity無法被釋放,關於記憶體洩漏可以檢視Android記憶體優化(三)避免可控的記憶體洩漏這篇文章。

2.2 DDMS生成hpof檔案

生成hpof檔案主要分為以下幾個步驟:

  1. 在Android Studio中開啟DDMS,執行程式。
  2. 在Devices中選擇要分析的應用程式程式,點選Update Heap按鈕(裝有一半綠色液體的圓柱體)開始進行追蹤。
  3. 進行可能發生記憶體問題的操作(本文的例子就是不斷的切換橫豎屏)。
  4. 點選Dump HPROP File按鈕結束追蹤,生成並儲存hprof檔案,如下圖所示。

DDMS生成的hprof檔案並不是標準的,還需要將它轉換為標準的hprof檔案,這樣才會被MAT識別從而進行分析,可以使用SDK自帶的hprof-conv進行轉換,它的路徑在sdk/platform-tools中,進入到該路徑執行以下語句即可:

hprof-conv D:\before.hprof D:\after.hprof複製程式碼

其中 D:\before.hprof 是要轉換的hprof檔案路徑,D:\after.hprof 則是轉換後hprof檔案的儲存路徑。

2.3 Memory Monitor生成hpof檔案

除了用DDMS來生成hpof檔案,還可以用AS的Memory Monitor來生成hpof檔案。
生成hpof檔案主要分為一下幾個步驟:

  1. 在Android Monitor中選擇要分析的應用程式程式。
  2. 進行可能發生記憶體問題的操作(本文的例子就是不斷的切換橫豎屏)。
  3. 點選Dump Java Heap按鈕,生成hprof檔案,如下圖所示。

Memory Monitor生成的hpof檔案也不是標準的,AS提供了便捷的轉換方式:Memory Monitor生成的hpof檔案都會顯示在AS左側的Captures標籤中,在Captures標籤中選擇要轉換的hpof檔案,並點選滑鼠右鍵,在彈出的選單中選擇Export to standard.hprof選項,即可匯出標準的hpof檔案,如下圖所示。

QQ截圖20170810232344.png
QQ截圖20170810232344.png

3.MAT分析hpof檔案

用MAT開啟標準的hpof檔案,選擇Leak Suspects Report選項。這時MAT就會生成報告,這個報告分為兩個標籤頁,一個是Overview,一個是Leak Suspects(記憶體洩漏猜想),如下圖所示。


Leak Suspects中會給出了MAT認為可能出現記憶體洩漏問題的地方,上圖共給出了3個記憶體洩漏猜想,通過點選每個記憶體洩漏猜想的Details可以看到更深入的分析清理情況。如果記憶體洩漏不是特別的明顯,通過Leak Suspects是很難發現記憶體洩漏的位置。

開啟Overview標籤頁,首先看到的是一個餅狀圖,它主要用來顯示記憶體的消耗,餅狀圖的彩色區域代表被分配的記憶體,灰色區域的則是空閒記憶體,點選每個彩色區域可以看到這塊區域的詳細資訊,如下圖所示。

再往下看,Actions一欄的下面列出了MAT提供的四種Action,其中分析記憶體洩漏最常用的就是Histogram和Dominator Tree。我們點選Actions中給出的連結或者在MAT工具欄中就可以開啟Dorminator Tree和Histogram,如下圖所示。

其中左邊第二個選項是Histogram,第三個選項是Dorminator Tree,第四個是OQL,下面分別對它們進行介紹。

3.1 Dominator Tree

Dorminator Tree意味支配樹,從名稱就可以看出Dorminator Tree更善於去分析物件的引用關係。

圖中可以看出Dorminator Tree有三列資料。

  • Shallow Heap:物件自身佔用的記憶體大小,不包括它引用的物件。如果是陣列型別的物件,它的大小是陣列元素的型別和陣列長度決定。如果是非陣列型別的物件,它的大小由其成員變數的數量和型別決定。
  • Retained Heap:一個物件的Retained Set所包含物件所佔記憶體的總大小。換句話說,Retained Heap就是當前物件被GC後,從Heap上總共能釋放掉的記憶體。

Retained Set指的是這個物件本身和他持有引用的物件以及這些引用物件的Retained Set所佔記憶體大小的總和,官方的圖解如下所示。

從圖中可以看出E的Retained Set為E和G。C的Retained Set為C、D、E、F、G、H。
MAT所定義的支配樹就是從上圖的引用樹演化而來。在引用樹當中,一條到Y的路徑必然會經過X,這就是X支配Y。X直接支配Y則指的是在所有支配Y的物件中,X是Y最近的一個物件。支配樹就是反映的這種直接支配關係,在支配樹中,父節點直接支配子節點。下圖就是官方提供的一個從引用樹到支配樹的轉換示意圖。

C直接支配D、E,因此C是D、E的父節點,這一點根據上面的闡述很容易得出結論。C直接支配H,這可能會有些疑問,能到達H的主要有兩條路徑,而這兩條路徑FD和GE都不是必須要經過的節點,只有C滿足了這一點,因此C直接支配H,C就是H的父節點。通過支配樹,我們就可以很容易的分析一個物件的Retained Set,比如E被回收,則會釋放E、G的記憶體,而不會釋放H的記憶體,因為F可能還引用著H,只有C被回收,H的記憶體才會被釋放。

這裡對支配樹進行了講解,我們可以得出一個結論:通過MAT提供的Dominator Tree,可以很清晰的得到一個物件的直接支配物件,如果直接支配物件中出現了不該有的物件,就說明發生了記憶體洩漏。
在Dominator Tree的頂部Regex可以輸入過濾條件(支援正規表示式),如果是查詢Activity記憶體洩漏,可以在Regex中輸入Activity的名稱,比如我們這個例子可以輸入MainActivity,效果如下圖所示。


Dominator Tree中列出了很多MainActivity例項,MainActivity是不該有這麼多例項的,基本可以斷定發生了記憶體洩漏,具體記憶體洩漏的原因,可以檢視GC引用鏈。在MainActivity一項單擊滑鼠右鍵,選擇Merge Shortest Paths to GC Root,如下圖所示。

Merge Shortest Paths to GC Root選項主要用來顯示距離GC Root最短的路徑,根據引用型別會有多種選項,比如with all references就是包含所有的引用,這裡我們選擇exclude all phantom/weak/soft etc. references,因為這個選項排除了虛引用、弱引用和軟引用,這些引用一般是可以被回收的。這時MAT就會給出MainActivity的GC引用鏈。


引用MainActivity的是LeakThread,this$0的含義就是內部類自動保留的一個指向所在外部類的引用,而這個外部類就是MainActivity,這將會導致MainActivity無法被GC。

3.2 Histogram

Histogram與Dominator Tree不同的是,Dominator Tree是在物件例項的角度上進行分析,注重引用關係分析,而Histogram則在類的角度上進行分析,注重量的分析。
Histogram中的內容如下圖所示。

可以看到Histogram中共用四列資料,關於Shallow Heap和Shallow Heap的含義我們在3.1節已經知道了,剩餘的 Class Name代表類名,Objects代表物件例項的個數。

在Histogram的頂部Regex同樣可以輸入過濾條件,這裡同樣輸入MainActivity,效果如下圖所示。

MainActivity和LeakThread例項各為11個,基本上可以斷定發生了記憶體洩漏。具體記憶體洩漏的原因,同樣可以檢視GC引用鏈。在MainActivity一項單擊滑鼠右鍵,選擇Merge Shortest Paths to GC Root,並在選項中選擇exclude all phantom/weak/soft etc. references如下圖所示。


得出的結果和3.1節是相同的,引用MainActivity的是LeakThread,這導致了MainActivity無法被GC。

3.3 OQL

OQL全稱為Object Query Language,類似於SQL語句的查詢語言,能夠用來查詢當前記憶體中滿足指定條件的所有的物件。它的查詢語句的基本格式為:

 SELECT * FROM [ INSTANCEOF ]    <class_name> [ WHERE <filter-expression>]複製程式碼

當我們輸入select * from instanceof android.app.Activity並按下F5時(或者按下工具欄的紅色歎號),會將當前記憶體中所有Activity都顯示出來,如下圖所示。


如果Activty比較多,或者你想查詢具體的類,可以直接輸入具體類的完整名稱:

select * from com.example.liuwangshu.leak.MainActivity複製程式碼

通過檢視GC引用鏈也可以找到記憶體洩漏的原因。關於OQL語句有很多用法,具體可以檢視官方文件

3.4 對比hpof檔案

因為我們這個例子很簡單,可以通過上面的方法來找到記憶體洩漏的原因,但是複雜的情況就需要通過對比hpof檔案來進行分析了。使用步驟為:

  1. 操作應用,生成第一個hpof檔案。
  2. 進行一段時間操作,再生成第二個hpof檔案。
  3. 用MAT開啟這兩個hpof檔案。
  4. 將第一個和第二個hpof檔案的Dominator Tree或者Histogram新增到Compare Basket中,如下圖所示。
  5. 在Compare Basket中點選紅色歎號按鈕生成Compared Tables,Compared Tables如下圖所示。

在Compared Tables也有頂部Regex,輸入MainActivity進行篩選。

可以看到MainActivity在這一過程中增加了6個,MainActivity的例項不應該增加的,這說明發生了記憶體洩漏,可以通過檢視GC引用鏈來找到記憶體洩漏的具體的原因。

除了上面的對比方法,Histogram還可以通過工具欄的對比按鈕來進行對比:

生成的結果和Compared Tables類似,我們輸入MainActivity進行篩選:

可以看到第二個hpof檔案比第一個hpof檔案多了6個MainActivity例項。

MAT還有很多功能,這裡也只介紹了常用的功能,其他的功能就需要讀者在使用過程中去發現並積累。

參考資料
《Android群英傳 神兵利器》
《Android應用效能優化最佳實踐》
《高效能Android應用開發》
利用MAT進行記憶體洩露分析
Android最佳效能實踐(二)——分析記憶體的使用情況
Memory Analyzer


歡迎關注我的微信公眾號,第一時間獲得部落格更新提醒,以及更多成體系的Android相關原創技術乾貨。
掃一掃下方二維碼或者長按識別二維碼,即可關注。

相關文章