一、概述
記憶體一直都是效能優化的重點,今天我們主要介紹如何使用Android Studio
生成分析hprof
報表,並使用MAT
分析結果,在介紹之前,首先需要感謝Gracker
,本文的分析大多數都是來自於它的這篇文章:
二、獲取記憶體快照並分析
2.1 獲取記憶體快照
為了便於大家理解,我們先編寫一個用於除錯的單例MemorySingleton
,它內部包含一個成員變數ObjectA
,而ObjectA
又包含了ObjectB
和ObjectC
,以及一個長度為4096
的int
陣列,ObjectB
和ObjectC
各自包含了一個ObjectD
,ObjectD
中包含了一個長度為4096
的int
陣列,在Activity
的onCreate()
中,我們初始化這個單例物件。
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
一欄中,可以看到應用佔用的記憶體數值,它的介面分為幾個部分:
2
主動觸發一次垃圾回收,再點選3
來獲得記憶體快照,等待一段時間後,會在視窗的左上部分獲得字尾為hprof
的分析報表:
在生成的hprof
上點選右鍵,就可以匯出為標準的hprof
用於MAT
分析,在螢幕的右邊,Android Studio
也提供了分析的介面,今天我們先不介紹它,而是匯出成MAT
可識別的分析報表。
2.2 使用MAT
分析報表
執行MAT
,開啟我們匯出的標準hprof
檔案:
Overview
介面,我們主要關注的是Actions
部分:
Actions
包含了四個部分,點選它可以得到不同的檢視:
Histogram
:Dominator Tree
Top Consumers
Duplicate Classes
在平時的分析當中,主要用到前兩個檢視,下面我們就來依次看一下怎麼使用這兩個檢視來進行分析記憶體的使用情況。
2.2.1 Histogram
點開Histogram
之後,會得到以下的介面:
MemorySingleton
中定義的成員物件為例,來一起理解一下它們的含義:
Objects
:表示該類在記憶體當中的物件個數。 這一列比較好理解,ObjectA
包含了ObjectB
和ObjectC
兩個成員變數,而它們又各自包含了ObjectD
,因此記憶體當中有2
個ObjectD
物件。Shallow Heap
這一列中文翻譯過來是“淺堆”,表示的是物件自身所佔用的記憶體大小,不包括它所引用的物件的記憶體大小。舉例來說,ObjectA
包含了int[]
、ObjectB
和ObjectC
這三個引用,但是它並不包括這三個引用所指向的int[]
陣列、ObjectB
物件和ObjectC
物件,它的大小為24
個位元組,ObjectB/C/D
也是同理。Retained Heap
這一列中文翻譯過來是“保留堆”,也就是當該物件被垃圾回收器回收之後,會釋放的記憶體大小。舉例來說,如果ObjectA
被回收了之後,那麼ObjectB
和ObjectC
也就沒有物件繼續引用它了,因此它也被回收,它們各自內部的ObjectD
也會被回收,如下圖所示: 因為ObjectA
被回收之後,它內部的int[]
陣列,以及ObjectB/ObjectC
所包含的ObjectD
的int[]
陣列所佔用的記憶體都會被回收,也就是:
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];
}
複製程式碼
我們重新抓取一次記憶體快照,那麼情況就變為了:
可以看到ObjectE
的Retained Heap
大小僅僅為16
位元組,和它的Shallow Heap
相同,這是因為它內部的成員變數objectF
所引用的ObjectF
,也同時被MemorySingleton
中的成員變數objectF
所引用,因此ObjectE
的釋放並不會導致objectF
物件被回收。
總結一下,Histogram
是從類的角度來觀察整個記憶體區域的,它會列出在記憶體當中,每個類的例項個數和記憶體佔用情況。
分析完這三個比較重要的列含義之後,我們再來看一下通過右鍵點選某個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
則是通過“引用樹”的方式來展現記憶體的使用情況的,通俗點來說,它是站在物件的角度來觀察記憶體的使用情況的。例如下圖,只有MemorySingleton
的Retain 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
的引用鏈,就可以分析出它為什麼沒有被回收了:
三、小結
通過Android Studio
和MAT
結合,我們就可以獲得某一時刻記憶體的使用情況,這樣我們很好地定位記憶體問題,是每個開發者必須要掌握的工具!
更多文章,歡迎訪問我的 Android 知識梳理系列:
- Android 知識梳理目錄:www.jianshu.com/p/fd82d1899…
- 個人主頁:lizejun.cn
- 個人知識總結目錄:lizejun.cn/categories/