效能優化工具知識梳理(5) MAT

澤毛發表於2017-12-21

一、概述

記憶體一直都是效能優化的重點,今天我們主要介紹如何使用Android Studio生成分析hprof報表,並使用MAT分析結果,在介紹之前,首先需要感謝Gracker,本文的分析大多數都是來自於它的這篇文章:

http://www.jianshu.com/p/d8e247b1e7b2

二、獲取記憶體快照並分析

2.1 獲取記憶體快照

為了便於大家理解,我們先編寫一個用於除錯的單例MemorySingleton,它內部包含一個成員變數ObjectA,而ObjectA又包含了ObjectBObjectC,以及一個長度為4096int陣列,ObjectBObjectC各自包含了一個ObjectDObjectD中包含了一個長度為4096int陣列,在ActivityonCreate()中,我們初始化這個單例物件。

public class MemorySingleton {
    private static MemorySingleton sInstance;
    private ObjectA objectA;
    
    public static synchronized MemorySingleton getInstance() {
        if (sInstance == null) {
            sInstance = new MemorySingleton();
        }
        return sInstance;
    }

    private MemorySingleton() {
        objectA = new ObjectA();
    }

}

public class ObjectA {
    private int[] dataOfA = new int[4096];
    private ObjectB objectB = new ObjectB();
    private ObjectC objectC = new ObjectC();
}

public class ObjectB {
    private ObjectD objectD = new ObjectD();
}

public class ObjectC {
    private ObjectD objectD = new ObjectD();
}

public class ObjectD {
    private int[] dataOfD = new int[4096];
}

public class MemoryActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory);
        MemorySingleton.getInstance();
    }
}
複製程式碼

Android Studio最下方的Monitors/Memory一欄中,可以看到應用佔用的記憶體數值,它的介面分為幾個部分:

效能優化工具知識梳理(5)   MAT
我們先點選2主動觸發一次垃圾回收,再點選3來獲得記憶體快照,等待一段時間後,會在視窗的左上部分獲得字尾為hprof的分析報表:
效能優化工具知識梳理(5)   MAT
在生成的hprof上點選右鍵,就可以匯出為標準的hprof用於MAT分析,在螢幕的右邊,Android Studio也提供了分析的介面,今天我們先不介紹它,而是匯出成MAT可識別的分析報表。
效能優化工具知識梳理(5)   MAT

2.2 使用MAT分析報表

執行MAT,開啟我們匯出的標準hprof檔案:

效能優化工具知識梳理(5)   MAT
經過一段時間的轉換之後,會得到下面的Overview介面,我們主要關注的是Actions部分:
效能優化工具知識梳理(5)   MAT
Actions包含了四個部分,點選它可以得到不同的檢視:

  • Histogram
  • Dominator Tree
  • Top Consumers
  • Duplicate Classes

在平時的分析當中,主要用到前兩個檢視,下面我們就來依次看一下怎麼使用這兩個檢視來進行分析記憶體的使用情況。

2.2.1 Histogram

點開Histogram之後,會得到以下的介面:

效能優化工具知識梳理(5)   MAT
這個檢視中提供了多種方式來對物件進行分類,這裡為了分析方便,我們選擇按包名進行分類:
效能優化工具知識梳理(5)   MAT
要理解這個檢視,最關鍵的是要明白紅色矩形框中各列的含義,下面,我們就以在MemorySingleton中定義的成員物件為例,來一起理解一下它們的含義:
效能優化工具知識梳理(5)   MAT

  • Objects:表示該類在記憶體當中的物件個數。 這一列比較好理解,ObjectA包含了ObjectBObjectC兩個成員變數,而它們又各自包含了ObjectD,因此記憶體當中有2ObjectD物件。
  • Shallow Heap 這一列中文翻譯過來是“淺堆”,表示的是物件自身所佔用的記憶體大小,不包括它所引用的物件的記憶體大小。舉例來說,ObjectA包含了int[]ObjectBObjectC這三個引用,但是它並不包括這三個引用所指向的int[]陣列、ObjectB物件和ObjectC物件,它的大小為24個位元組,ObjectB/C/D也是同理。
  • Retained Heap 這一列中文翻譯過來是“保留堆”,也就是當該物件被垃圾回收器回收之後,會釋放的記憶體大小。舉例來說,如果ObjectA被回收了之後,那麼ObjectBObjectC也就沒有物件繼續引用它了,因此它也被回收,它們各自內部的ObjectD也會被回收,如下圖所示:
    效能優化工具知識梳理(5)   MAT
    因為ObjectA被回收之後,它內部的int[]陣列,以及ObjectB/ObjectC所包含的ObjectDint[]陣列所佔用的記憶體都會被回收,也就是:
retained heap of ObjectA = shallow heap of ObjectA + int[4096] +retained heap of ObjectB + retained heap of ObjectC
複製程式碼

下面,我們考慮一種比較複雜的情況,我們的引用情況變為了下面這樣:

效能優化工具知識梳理(5)   MAT
對應的程式碼為:

public class MemorySingleton {
    private static MemorySingleton sInstance;
    private ObjectA objectA;
    private ObjectE objectE;
    private ObjectF objectF;
    public static synchronized MemorySingleton getInstance() {
        if (sInstance == null) {
            sInstance = new MemorySingleton();
        }
        return sInstance;
    }
    private MemorySingleton() {
        objectA = new ObjectA();
        objectE = new ObjectE();
        objectF = new ObjectF();
        objectE.setObjectF(objectF);
    }
}

public class ObjectE {
    private ObjectF objectF;
    public void setObjectF(ObjectF objectF) {
        this.objectF = objectF;
    }
}

public class ObjectF {
    private int[] dataInF = new int[4096];
}
複製程式碼

我們重新抓取一次記憶體快照,那麼情況就變為了:

效能優化工具知識梳理(5)   MAT
可以看到ObjectERetained Heap大小僅僅為16位元組,和它的Shallow Heap相同,這是因為它內部的成員變數objectF所引用的ObjectF,也同時被MemorySingleton中的成員變數objectF所引用,因此ObjectE的釋放並不會導致objectF物件被回收。

總結一下,Histogram是從類的角度來觀察整個記憶體區域的,它會列出在記憶體當中,每個類的例項個數和記憶體佔用情況。

分析完這三個比較重要的列含義之後,我們再來看一下通過右鍵點選某個Item之後的彈出列表中的選項:

  • List Objects
    效能優化工具知識梳理(5)   MAT
  • incomming reference表示它被那些物件所引用
    效能優化工具知識梳理(5)   MAT
  • outgoing則表示它所引用的物件
    效能優化工具知識梳理(5)   MAT
  • Show objects by class 和上面的選項類似,只不過列出的是類名。
  • Merge Shortest Paths to GC Roots,我們可以選擇排除一些型別的引用:
    效能優化工具知識梳理(5)   MAT
    Gc根節點的最短路徑,以ObjectD為例,它的兩個例項物件到Gc Roots的路徑如下,這個選項很重要,當需要定位記憶體洩漏問題的時候,我們一般都是通過這個工具:
    效能優化工具知識梳理(5)   MAT

2.2.2 dominator_tree

dominator_tree則是通過“引用樹”的方式來展現記憶體的使用情況的,通俗點來說,它是站在物件的角度來觀察記憶體的使用情況的。例如下圖,只有MemorySingletonRetain Heap的大小被計算出來,而它內部的成員變數的Retain Heap都為0

效能優化工具知識梳理(5)   MAT
要獲得更詳細的情況,我們需要通過它的outgoing,也就是它所引用的物件來分析:
效能優化工具知識梳理(5)   MAT
可以看到它的outgoing檢視中有兩個objectF,但是它們都是指向同一片記憶體空間@0x12d8d7f0,通過這個檢視,我們可以列出那麼佔用記憶體較多的物件,然後一步步地分析,看究竟是什麼導致了它所佔用如此多的記憶體,以此達到優化效能的目的。

2.3 分析Activity記憶體洩漏問題

在平時的開發中,我們最容易遇到的就是Activity記憶體洩漏,下面,我們模擬一下這種情況,並演示一下通過MAT來分析記憶體洩漏的原因,首先,我們編寫一段會導致記憶體洩漏的程式碼:


public class MemorySingleton {
    private static MemorySingleton sInstance;
    private Context mContext;
    public static synchronized MemorySingleton getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new MemorySingleton(context);
        }
        return sInstance;
    }
    private MemorySingleton(Context context) {
        mContext = context;
    }
}

public class MemoryActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory);
        MemorySingleton.getInstance(this);
    }
}
複製程式碼

我們啟動MemoryActivity之後,然後按back退出,按照常理來說,此時它應當被回收,但是由於它被MemorySingleton中的mContext所引用,因此它並不能被回收,此時的記憶體快照為:

效能優化工具知識梳理(5)   MAT
我們通過檢視它到Gc Roots的引用鏈,就可以分析出它為什麼沒有被回收了:
效能優化工具知識梳理(5)   MAT

三、小結

通過Android StudioMAT結合,我們就可以獲得某一時刻記憶體的使用情況,這樣我們很好地定位記憶體問題,是每個開發者必須要掌握的工具!


更多文章,歡迎訪問我的 Android 知識梳理系列:

相關文章