性能優(yōu)化工具知識(shí)梳理(1) - TraceView
性能優(yōu)化工具知識(shí)梳理(2) - Systrace
性能優(yōu)化工具知識(shí)梳理(3) - 調(diào)試GPU過(guò)度繪制 & GPU呈現(xiàn)模式分析
性能優(yōu)化工具知識(shí)梳理(4) - Hierarchy Viewer
性能優(yōu)化工具知識(shí)梳理(5) - MAT
性能優(yōu)化工具知識(shí)梳理(6) - Memory Monitor & Heap Viewer & Allocation Tracker
性能優(yōu)化工具知識(shí)梳理(7) - LeakCanary
性能優(yōu)化工具知識(shí)梳理(8) - Lint
一、概述
內(nèi)存一直都是性能優(yōu)化的重點(diǎn),今天我們主要介紹如何使用Android Studio
生成分析hprof
報(bào)表混狠,并使用MAT
分析結(jié)果桐腌,在介紹之前,首先需要感謝Gracker
裁蚁,本文的分析大多數(shù)都是來(lái)自于它的這篇文章:
二矢渊、獲取內(nèi)存快照并分析
2.1 獲取內(nèi)存快照
為了便于大家理解,我們先編寫一個(gè)用于調(diào)試的單例MemorySingleton
枉证,它內(nèi)部包含一個(gè)成員變量ObjectA
矮男,而ObjectA
又包含了ObjectB
和ObjectC
,以及一個(gè)長(zhǎng)度為4096
的int
數(shù)組室谚,ObjectB
和ObjectC
各自包含了一個(gè)ObjectD
毡鉴,ObjectD
中包含了一個(gè)長(zhǎng)度為4096
的int
數(shù)組崔泵,在Activity
的onCreate()
中,我們初始化這個(gè)單例對(duì)象猪瞬。
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
一欄中憎瘸,可以看到應(yīng)用占用的內(nèi)存數(shù)值,它的界面分為幾個(gè)部分:
我們先點(diǎn)擊
2
主動(dòng)觸發(fā)一次垃圾回收陈瘦,再點(diǎn)擊3
來(lái)獲得內(nèi)存快照幌甘,等待一段時(shí)間后,會(huì)在窗口的左上部分獲得后綴為hprof
的分析報(bào)表:在生成的
hprof
上點(diǎn)擊右鍵甘晤,就可以導(dǎo)出為標(biāo)準(zhǔn)的hprof
用于MAT
分析含潘,在屏幕的右邊,Android Studio
也提供了分析的界面线婚,今天我們先不介紹它遏弱,而是導(dǎo)出成MAT
可識(shí)別的分析報(bào)表。2.2 使用MAT
分析報(bào)表
運(yùn)行MAT
塞弊,打開(kāi)我們導(dǎo)出的標(biāo)準(zhǔn)hprof
文件:
經(jīng)過(guò)一段時(shí)間的轉(zhuǎn)換之后漱逸,會(huì)得到下面的
Overview
界面,我們主要關(guān)注的是Actions
部分:Actions
包含了四個(gè)部分游沿,點(diǎn)擊它可以得到不同的視圖:
-
Histogram
: Dominator Tree
Top Consumers
Duplicate Classes
在平時(shí)的分析當(dāng)中饰抒,主要用到前兩個(gè)視圖,下面我們就來(lái)依次看一下怎么使用這兩個(gè)視圖來(lái)進(jìn)行分析內(nèi)存的使用情況诀黍。
2.2.1 Histogram
點(diǎn)開(kāi)Histogram
之后袋坑,會(huì)得到以下的界面:
這個(gè)視圖中提供了多種方式來(lái)對(duì)對(duì)象進(jìn)行分類,這里為了分析方便眯勾,我們選擇按包名進(jìn)行分類:
要理解這個(gè)視圖枣宫,最關(guān)鍵的是要明白紅色矩形框中各列的含義,下面吃环,我們就以在
MemorySingleton
中定義的成員對(duì)象為例也颤,來(lái)一起理解一下它們的含義:-
Objects
:表示該類在內(nèi)存當(dāng)中的對(duì)象個(gè)數(shù)。
這一列比較好理解郁轻,ObjectA
包含了ObjectB
和ObjectC
兩個(gè)成員變量翅娶,而它們又各自包含了ObjectD
,因此內(nèi)存當(dāng)中有2
個(gè)ObjectD
對(duì)象好唯。 -
Shallow Heap
這一列中文翻譯過(guò)來(lái)是“淺堆”竭沫,表示的是對(duì)象自身所占用的內(nèi)存大小,不包括它所引用的對(duì)象的內(nèi)存大小骑篙。舉例來(lái)說(shuō)蜕提,ObjectA
包含了int[]
、ObjectB
和ObjectC
這三個(gè)引用替蛉,但是它并不包括這三個(gè)引用所指向的int[]
數(shù)組贯溅、ObjectB
對(duì)象和ObjectC
對(duì)象拄氯,它的大小為24
個(gè)字節(jié),ObjectB/C/D
也是同理它浅。 -
Retained Heap
這一列中文翻譯過(guò)來(lái)是“保留堆”译柏,也就是當(dāng)該對(duì)象被垃圾回收器回收之后,會(huì)釋放的內(nèi)存大小姐霍。舉例來(lái)說(shuō)鄙麦,如果ObjectA
被回收了之后,那么ObjectB
和ObjectC
也就沒(méi)有對(duì)象繼續(xù)引用它了镊折,因此它也被回收胯府,它們各自內(nèi)部的ObjectD
也會(huì)被回收,如下圖所示:
因?yàn)?code>ObjectA被回收之后恨胚,它內(nèi)部的int[]
數(shù)組骂因,以及ObjectB/ObjectC
所包含的ObjectD
的int[]
數(shù)組所占用的內(nèi)存都會(huì)被回收,也就是:
retained heap of ObjectA = shallow heap of ObjectA + int[4096] +retained heap of ObjectB + retained heap of ObjectC
下面赃泡,我們考慮一種比較復(fù)雜的情況寒波,我們的引用情況變?yōu)榱讼旅孢@樣:
對(duì)應(yīng)的代碼為:
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];
}
我們重新抓取一次內(nèi)存快照,那么情況就變?yōu)榱耍?br>
可以看到
ObjectE
的Retained Heap
大小僅僅為16
字節(jié)升熊,和它的Shallow Heap
相同俄烁,這是因?yàn)樗鼉?nèi)部的成員變量objectF
所引用的ObjectF
,也同時(shí)被MemorySingleton
中的成員變量objectF
所引用级野,因此ObjectE
的釋放并不會(huì)導(dǎo)致objectF
對(duì)象被回收页屠。
總結(jié)一下,Histogram
是從類的角度來(lái)觀察整個(gè)內(nèi)存區(qū)域的蓖柔,它會(huì)列出在內(nèi)存當(dāng)中辰企,每個(gè)類的實(shí)例個(gè)數(shù)和內(nèi)存占用情況。
分析完這三個(gè)比較重要的列含義之后渊抽,我們?cè)賮?lái)看一下通過(guò)右鍵點(diǎn)擊某個(gè)Item
之后的彈出列表中的選項(xiàng):
-
List Objects
:
-
incomming reference
表示它被那些對(duì)象所引用
-
outgoing
則表示它所引用的對(duì)象
-
Show objects by class
和上面的選項(xiàng)類似蟆豫,只不過(guò)列出的是類名议忽。 -
Merge Shortest Paths to GC Roots
懒闷,我們可以選擇排除一些類型的引用:
到Gc
根節(jié)點(diǎn)的最短路徑,以ObjectD
為例栈幸,它的兩個(gè)實(shí)例對(duì)象到Gc Roots
的路徑如下愤估,這個(gè)選項(xiàng)很重要,當(dāng)需要定位內(nèi)存泄漏問(wèn)題的時(shí)候速址,我們一般都是通過(guò)這個(gè)工具:
2.2.2 dominator_tree
dominator_tree
則是通過(guò)“引用樹”的方式來(lái)展現(xiàn)內(nèi)存的使用情況的玩焰,通俗點(diǎn)來(lái)說(shuō),它是站在對(duì)象的角度來(lái)觀察內(nèi)存的使用情況的芍锚。例如下圖昔园,只有MemorySingleton
的Retain Heap
的大小被計(jì)算出來(lái)蔓榄,而它內(nèi)部的成員變量的Retain Heap
都為0
:
要獲得更詳細(xì)的情況,我們需要通過(guò)它的
outgoing
默刚,也就是它所引用的對(duì)象來(lái)分析:可以看到它的
outgoing
視圖中有兩個(gè)objectF
甥郑,但是它們都是指向同一片內(nèi)存空間@0x12d8d7f0
,通過(guò)這個(gè)視圖荤西,我們可以列出那么占用內(nèi)存較多的對(duì)象澜搅,然后一步步地分析,看究竟是什么導(dǎo)致了它所占用如此多的內(nèi)存邪锌,以此達(dá)到優(yōu)化性能的目的勉躺。
2.3 分析Activity
內(nèi)存泄漏問(wèn)題
在平時(shí)的開(kāi)發(fā)中,我們最容易遇到的就是Activity
內(nèi)存泄漏觅丰,下面饵溅,我們模擬一下這種情況,并演示一下通過(guò)MAT
來(lái)分析內(nèi)存泄漏的原因妇萄,首先概说,我們編寫一段會(huì)導(dǎo)致內(nèi)存泄漏的代碼:
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);
}
}
我們啟動(dòng)MemoryActivity
之后,然后按back
退出嚣伐,按照常理來(lái)說(shuō)糖赔,此時(shí)它應(yīng)當(dāng)被回收,但是由于它被MemorySingleton
中的mContext
所引用轩端,因此它并不能被回收放典,此時(shí)的內(nèi)存快照為:
我們通過(guò)查看它到
Gc Roots
的引用鏈,就可以分析出它為什么沒(méi)有被回收了:三基茵、小結(jié)
通過(guò)Android Studio
和MAT
結(jié)合奋构,我們就可以獲得某一時(shí)刻內(nèi)存的使用情況,這樣我們很好地定位內(nèi)存問(wèn)題拱层,是每個(gè)開(kāi)發(fā)者必須要掌握的工具弥臼!
更多文章,歡迎訪問(wèn)我的 Android 知識(shí)梳理系列:
- Android 知識(shí)梳理目錄:http://www.reibang.com/p/fd82d18994ce