Android內存優化(五)詳解內存分析工具MAT

相關文章
Android性能優化系列
Java虛擬機系列html

前言

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

1.概述

在進行內存分析時,咱們可使用Memory Monitor和Heap Dump來觀察內存的使用狀況、使用Allocation Tracker來跟蹤內存分配的狀況,也能夠經過這些工具來找到疑似發生內存泄漏的位置。可是若是想要深刻的進行分析並肯定內存泄漏,就要分析
疑似發生內存泄漏時所生成堆存儲文件。堆存儲文件可使用DDMS或者Memory Monitor來生成,輸出的文件格式爲hpof,而MAT就是來分析堆存儲文件的。
MAT,全稱爲Memory Analysis Tool,是對內存進行詳細分析的工具,它是Eclipse的插件,若是用Android Studio進行開發則須要單獨下載它,下載地址爲:eclipse.org/mat/,這篇文章MA…android

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內存優化(三)避免可控的內存泄漏這篇文章。express

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文件的保存路徑。bash

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文件,以下圖所示。
app

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相關原創技術乾貨。
掃一掃下方二維碼或者長按識別二維碼,便可關注。

相關文章
相關標籤/搜索