內存一直都是性能優化的重點,今天咱們主要介紹如何使用Android Studio
生成分析hprof
報表,並使用MAT
分析結果,在介紹以前,首先須要感謝Gracker
,本文的分析大多數都是來自於它的這篇文章:數組
爲了便於你們理解,咱們先編寫一個用於調試的單例MemorySingleton
,它內部包含一個成員變量ObjectA
,而ObjectA
又包含了ObjectB
和ObjectC
,以及一個長度爲4096
的int
數組,ObjectB
和ObjectC
各自包含了一個ObjectD
,ObjectD
中包含了一個長度爲4096
的int
數組,在Activity
的onCreate()
中,咱們初始化這個單例對象。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
可識別的分析報表。
MAT
分析報表運行MAT
,打開咱們導出的標準hprof
文件: ide
Overview
界面,咱們主要關注的是
Actions
部分:
Actions
包含了四個部分,點擊它能夠獲得不一樣的視圖:
Histogram
:Dominator Tree
Top Consumers
Duplicate Classes
在平時的分析當中,主要用到前兩個視圖,下面咱們就來依次看一下怎麼使用這兩個視圖來進行分析內存的使用狀況。工具
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];
}
複製代碼
咱們從新抓取一次內存快照,那麼狀況就變爲了: ui
能夠看到ObjectE
的
Retained 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
的路徑以下,這個選項很重要,當須要定位內存泄漏問題的時候,咱們通常都是經過這個工具:
dominator_tree
dominator_tree
則是經過「引用樹」的方式來展示內存的使用狀況的,通俗點來講,它是站在對象的角度來觀察內存的使用狀況的。例以下圖,只有MemorySingleton
的Retain Heap
的大小被計算出來,而它內部的成員變量的Retain Heap
都爲0
:
outgoing
,也就是它所引用的對象來分析:
能夠看到它的
outgoing
視圖中有兩個
objectF
,可是它們都是指向同一片內存空間
@0x12d8d7f0
,經過這個視圖,咱們能夠列出那麼佔用內存較多的對象,而後一步步地分析,看到底是什麼致使了它所佔用如此多的內存,以此達到優化性能的目的。
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
結合,咱們就能夠得到某一時刻內存的使用狀況,這樣咱們很好地定位內存問題,是每一個開發者必需要掌握的工具!