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

1、概述

內存一直都是性能優化的重點,今天咱們主要介紹如何使用Android Studio生成分析hprof報表,並使用MAT分析結果,在介紹以前,首先須要感謝Gracker,本文的分析大多數都是來自於它的這篇文章:數組

http://www.jianshu.com/p/d8e247b1e7b2性能優化

2、獲取內存快照並分析

2.1 獲取內存快照

爲了便於你們理解,咱們先編寫一個用於調試的單例MemorySingleton,它內部包含一個成員變量ObjectA,而ObjectA又包含了ObjectBObjectC,以及一個長度爲4096int數組,ObjectBObjectC各自包含了一個ObjectDObjectD中包含了一個長度爲4096int數組,在ActivityonCreate()中,咱們初始化這個單例對象。bash

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一欄中,能夠看到應用佔用的內存數值,它的界面分爲幾個部分:dom

咱們先點擊 2主動觸發一次垃圾回收,再點擊 3來得到內存快照,等待一段時間後,會在窗口的左上部分得到後綴爲 hprof的分析報表:
在生成的 hprof上點擊右鍵,就能夠導出爲標準的 hprof用於 MAT分析,在屏幕的右邊, Android Studio也提供了分析的界面,今天咱們先不介紹它,而是導出成 MAT可識別的分析報表。

2.2 使用MAT分析報表

運行MAT,打開咱們導出的標準hprof文件: ide

通過一段時間的轉換以後,會獲得下面的 Overview界面,咱們主要關注的是 Actions部分:
Actions包含了四個部分,點擊它能夠獲得不一樣的視圖:

  • Histogram
  • Dominator Tree
  • Top Consumers
  • Duplicate Classes

在平時的分析當中,主要用到前兩個視圖,下面咱們就來依次看一下怎麼使用這兩個視圖來進行分析內存的使用狀況。工具

2.2.1 Histogram

點開Histogram以後,會獲得如下的界面: 性能

這個視圖中提供了多種方式來對對象進行分類,這裏爲了分析方便,咱們選擇按包名進行分類:
要理解這個視圖,最關鍵的是要明白紅色矩形框中各列的含義,下面,咱們就以在 MemorySingleton中定義的成員對象爲例,來一塊兒理解一下它們的含義:

  • Objects:表示該類在內存當中的對象個數。 這一列比較好理解,ObjectA包含了ObjectBObjectC兩個成員變量,而它們又各自包含了ObjectD,所以內存當中有2ObjectD對象。
  • Shallow Heap 這一列中文翻譯過來是「淺堆」,表示的是對象自身所佔用的內存大小,不包括它所引用的對象的內存大小。舉例來講,ObjectA包含了int[]ObjectBObjectC這三個引用,可是它並不包括這三個引用所指向的int[]數組、ObjectB對象和ObjectC對象,它的大小爲24個字節,ObjectB/C/D也是同理。
  • Retained Heap 這一列中文翻譯過來是「保留堆」,也就是當該對象被垃圾回收器回收以後,會釋放的內存大小。舉例來講,若是ObjectA被回收了以後,那麼ObjectBObjectC也就沒有對象繼續引用它了,所以它也被回收,它們各自內部的ObjectD也會被回收,以下圖所示:
    由於ObjectA被回收以後,它內部的int[]數組,以及ObjectB/ObjectC所包含的ObjectDint[]數組所佔用的內存都會被回收,也就是:
retained heap of ObjectA = shallow heap of ObjectA + int[4096] +retained heap of ObjectB + retained heap of ObjectC
複製代碼

下面,咱們考慮一種比較複雜的狀況,咱們的引用狀況變爲了下面這樣: 優化

對應的代碼爲:

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];
}
複製代碼

咱們從新抓取一次內存快照,那麼狀況就變爲了: ui

能夠看到 ObjectERetained Heap大小僅僅爲 16字節,和它的 Shallow Heap相同,這是由於它內部的成員變量 objectF所引用的 ObjectF,也同時被 MemorySingleton中的成員變量 objectF所引用,所以 ObjectE的釋放並不會致使 objectF對象被回收。

總結一下,Histogram是從類的角度來觀察整個內存區域的,它會列出在內存當中,每一個類的實例個數和內存佔用狀況。this

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

  • List Objects
  • incomming reference表示它被那些對象所引用
  • outgoing則表示它所引用的對象
  • Show objects by class 和上面的選項相似,只不過列出的是類名。
  • Merge Shortest Paths to GC Roots,咱們能夠選擇排除一些類型的引用:
    Gc根節點的最短路徑,以ObjectD爲例,它的兩個實例對象到Gc Roots的路徑以下,這個選項很重要,當須要定位內存泄漏問題的時候,咱們通常都是經過這個工具:

2.2.2 dominator_tree

dominator_tree則是經過「引用樹」的方式來展示內存的使用狀況的,通俗點來講,它是站在對象的角度來觀察內存的使用狀況的。例以下圖,只有MemorySingletonRetain Heap的大小被計算出來,而它內部的成員變量的Retain Heap都爲0

要得到更詳細的狀況,咱們須要經過它的 outgoing,也就是它所引用的對象來分析:
能夠看到它的 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所引用,所以它並不能被回收,此時的內存快照爲:

咱們經過查看它到 Gc Roots的引用鏈,就能夠分析出它爲何沒有被回收了:

3、小結

經過Android StudioMAT結合,咱們就能夠得到某一時刻內存的使用狀況,這樣咱們很好地定位內存問題,是每一個開發者必需要掌握的工具!


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

相關文章
相關標籤/搜索