前言
要想做好內(nèi)存優(yōu)化工作叛本,就要掌握兩大部分的知識(shí)沪蓬,一部分是知道并理解內(nèi)存優(yōu)化相關(guān)的原理,另一部分就是善于運(yùn)用內(nèi)存分析的工具来候。本篇就來(lái)介紹內(nèi)存分析工具:Memory Monitor跷叉、Allocation Tracker和Heap Dump的使用方法。
1.Memory Monitor
在Android Studio(以下簡(jiǎn)稱AS)中Android Monitor是一個(gè)主窗口营搅,它包含了Logcat,云挟、Memory Monitor、CPU Monitor转质、 GPU Monitor和Network Monitor园欣。其中Memory Monitor可以輕松地監(jiān)視應(yīng)用程序的性能和內(nèi)存使用情況,以便于找到被分配的對(duì)象休蟹,定位內(nèi)存泄漏沸枯,并跟蹤連接設(shè)備中正在使用的內(nèi)存數(shù)量日矫。Memory Monitor可以報(bào)告出你的應(yīng)用程序的內(nèi)存分配情況, 更形象的呈現(xiàn)出應(yīng)用程序使用的內(nèi)存绑榴。它的作用如下:
- 實(shí)時(shí)顯示可用的和分配的Java內(nèi)存的圖表哪轿。
- 實(shí)時(shí)顯示垃圾收集(GC)事件。
- 啟動(dòng)垃圾收集事件翔怎。
- 快速測(cè)試應(yīng)用程序的緩慢是否與過(guò)度的垃圾收集事件有關(guān)窃诉。
- 快速測(cè)試應(yīng)用程序崩潰是否與內(nèi)存耗盡有關(guān)。
1.1 使用Memory Monitor
在使用Memory Monitor之前要確保手機(jī)開啟了開發(fā)者模式和USB調(diào)試赤套。
使用的步驟為:
1.運(yùn)行需要監(jiān)控的應(yīng)用程序飘痛。
2.點(diǎn)擊AS面板下面的Android圖標(biāo),并選擇Monitors選項(xiàng)容握。
如果Memory Monitor已經(jīng)運(yùn)行敦冬,效果如下圖所示(AS版本2.3.2)。
圖中的標(biāo)注的功能如下:
- Initiate GC(標(biāo)識(shí)1):用來(lái)手動(dòng)觸發(fā)GC唯沮。
- Dump Java heap(標(biāo)識(shí)2):保存內(nèi)存快照脖旱。
- Start/Stop Allocation Tracking(標(biāo)識(shí)3):打開Allocation Tracker工具(后面會(huì)介紹)。
- Free(標(biāo)識(shí)4):當(dāng)前應(yīng)用未分配的內(nèi)存大小介蛉。
- Allocated(標(biāo)識(shí)5):當(dāng)前應(yīng)用分配的內(nèi)存大小萌庆。
圖中y軸顯示當(dāng)前應(yīng)用的分配的內(nèi)存和未分配的內(nèi)存大小币旧;x軸表示經(jīng)過(guò)的時(shí)間践险。
1.2 大內(nèi)存申請(qǐng)與GC
從上圖可以看出,分配的內(nèi)存急劇上升吹菱,這就是大內(nèi)存分配的場(chǎng)景巍虫,我們要判斷這是否是合理的分配的內(nèi)存,是Bitmap還是其他的大數(shù)據(jù)鳍刷,并且對(duì)這種大數(shù)據(jù)進(jìn)行優(yōu)化占遥,減少內(nèi)存開銷。
接下來(lái)分配的內(nèi)存出現(xiàn)急劇下降输瓜,這表示垃圾收集事件瓦胎,用來(lái)釋放內(nèi)存。
1.3 內(nèi)存抖動(dòng)
內(nèi)存抖動(dòng)一般指在很短的時(shí)間內(nèi)發(fā)生了多次內(nèi)存分配和釋放尤揣,嚴(yán)重的內(nèi)存抖動(dòng)還會(huì)導(dǎo)致應(yīng)用程序卡頓搔啊。內(nèi)存抖動(dòng)出現(xiàn)原因主要是短時(shí)間頻繁的創(chuàng)建對(duì)象(可能在循環(huán)中創(chuàng)建對(duì)象),內(nèi)存為了應(yīng)對(duì)這種情況北戏,也會(huì)頻繁的進(jìn)行GC负芋,因此綜合起來(lái)就產(chǎn)生了內(nèi)存抖動(dòng),產(chǎn)生了如上圖般的鋸齒狀嗜愈。
2.Allocation Tracker
Allocation Tracker用來(lái)跟蹤內(nèi)存分配旧蛾,它允許你在執(zhí)行某些操作的同時(shí)監(jiān)視在何處分配對(duì)象惩猫,了解這些分配使你能夠調(diào)整與這些操作相關(guān)的方法調(diào)用,以優(yōu)化應(yīng)用程序性能和內(nèi)存使用蚜点。
Allocation Tracker能夠做到如下的事情:
- 顯示代碼分配對(duì)象類型、大小拌阴、分配線程和堆棧跟蹤的時(shí)間和位置绍绘。
- 通過(guò)重復(fù)的分配/釋放模式幫助識(shí)別內(nèi)存變化。
- 當(dāng)與 HPROF Viewer結(jié)合使用時(shí)迟赃,可以幫助你跟蹤內(nèi)存泄漏陪拘。例如,如果你在堆上看到一個(gè)bitmap對(duì)象纤壁,你可以使用Allocation Tracker來(lái)找到其分配的位置左刽。
2.1 使用Allocation Tracker
AS和DDMS中都有Allocation Tracker,這里會(huì)·介紹AS中的Allocation Tracke如何使用酌媒。首先要確保要確保手機(jī)開啟了開發(fā)者模式欠痴,并且開啟了USB調(diào)試。
使用的步驟為:
1.運(yùn)行需要監(jiān)控的應(yīng)用程序秒咨。
2.點(diǎn)擊AS面板下面的Android圖標(biāo)喇辽,并選擇Monitors選項(xiàng)。
3.點(diǎn)擊Start Allocation Tracking按鈕雨席,這時(shí)Start Allocation Tracking按鈕變?yōu)榱薙top Allocation Tracking按鈕菩咨。
4.操作應(yīng)用程序。
5.點(diǎn)擊Stop Allocation Tracking按鈕陡厘,結(jié)束快照抽米。這時(shí)Memory Monitor會(huì)顯示出捕獲快照的期間,如下圖所示糙置。
6.過(guò)幾秒后就會(huì)自動(dòng)打開一個(gè)窗口云茸,顯示當(dāng)前生成的alloc文件的內(nèi)存數(shù)據(jù)。
2.2 alloc文件分析
自動(dòng)打開的alloc文件窗口如下圖所示谤饭。
該alloc文件顯示以下信息:
| 列 | 說(shuō)明|
| :-------- :|: --------:|
| Method | 負(fù)責(zé)分配的Java方法|
| Count| 分配的實(shí)例總數(shù)|
| Total Size| 分配內(nèi)存的總字節(jié)數(shù)|
接著我們來(lái)分析標(biāo)紅框的內(nèi)容查辩,負(fù)責(zé)分配的Java方法為performLaunchActivity,內(nèi)存分配序列為2369网持,分配的對(duì)象為ActivityThread宜岛,分配的實(shí)例總數(shù)為300個(gè),分配內(nèi)存的總字節(jié)數(shù)為10512功舀。不了解performLaunchActivity方法和ActivityThread可以看Android深入四大組件這一系列的文章萍倡。
目前的菜單選項(xiàng)是Group by Method我們也可以選擇 Group By Allocator,如下圖所示辟汰。
為了更好的解釋圖中的信息列敲,這里給出測(cè)試的代碼阱佛,MainActivity和SecondActivity 的代碼如下所示。
MainActivity.java
public class MainActivity extends AppCompatActivity {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button =(Button)findViewById(R.id.bt_next);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this,SecondActivity.class));
}
});
}
}
SecondActivity.java
public class SecondActivity extends AppCompatActivity {
private static Object inner;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.bt_next);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
createInnerClass();
finish();
}
});
}
void createInnerClass() {
class InnerClass {
}
inner = new InnerClass();
}
}
其中SecondActivity是存在內(nèi)存泄漏的戴而,生成快照期間凑术,我的操作就是在MainActivity和SecondActivity跳轉(zhuǎn)了3次(點(diǎn)擊button 共6次)。這時(shí)我們回過(guò)頭來(lái)看上圖的紅框的信息所意,MainActivity總共分配了3個(gè)Intent實(shí)例淮逊,占用內(nèi)存為192字節(jié)。SecondActivity總共分配了6個(gè)實(shí)例扶踊,占用內(nèi)存為96字節(jié)泄鹏,其中分配了3個(gè)匿名內(nèi)部類OnClickListener的實(shí)例,3個(gè)InnerClass的實(shí)例秧耗。
我們可以選擇列表中的一項(xiàng)备籽,單擊鼠標(biāo)右鍵,在彈出的菜單中選擇jump to the source就可以跳轉(zhuǎn)到對(duì)應(yīng)的源文件中分井。
除此之外车猬,還可以點(diǎn)擊Show/Hide Chart按鈕來(lái)顯示數(shù)據(jù)的圖形化,如下圖所示尺锚。
3.Heap Dump
Heap Dump的主要功能就是查看不同的數(shù)據(jù)類型在內(nèi)存中的使用情況诈唬。它可以幫助你找到大對(duì)象,也可以通過(guò)數(shù)據(jù)的變化發(fā)現(xiàn)內(nèi)存泄漏缩麸。
3.1 使用Heap Dump
打開Android Device Monitor工具铸磅,在左邊Devices列表中選擇要查看的應(yīng)用程序進(jìn)程,點(diǎn)擊Update Heap按鈕(裝有一半綠色液體的圓柱體)杭朱,在右邊選擇Heap選項(xiàng)阅仔,并點(diǎn)擊Cause GC按鈕,就會(huì)開始顯示數(shù)據(jù)弧械。我們每次點(diǎn)擊Cause GC按鈕都會(huì)強(qiáng)制應(yīng)用程序進(jìn)行垃圾回收八酒,并將清理后的數(shù)據(jù)顯示在Heap工具中。如下圖所示刃唐。
從上圖可以看出羞迷,Heap工具共有三個(gè)區(qū)域,分別是總覽視圖(標(biāo)識(shí)1)画饥、詳情視圖(標(biāo)識(shí)2)和內(nèi)存分配柱狀圖(標(biāo)識(shí)2)衔瓮。
3.2 總覽視圖
其中總覽視圖可以查看整體的內(nèi)存情況,表中的顯示信息如下所示抖甘。
|列 | 說(shuō)明 |
| :-------- :|: --------:|
| Heap Size|堆棧分配給該應(yīng)用程序的內(nèi)存大小 |
| Allocated|已分配使用的內(nèi)存大小 |
| Free|空閑的內(nèi)存大小 |
| %Used|當(dāng)前Heap的使用率(Allocated/Heap Size) |
| #Objects|對(duì)象的數(shù)量 |
結(jié)合上表和上圖热鞍,我們?cè)诳傆[視圖獲得的信息就是:堆棧分配給當(dāng)前的應(yīng)用程序的內(nèi)存大小為2.346MB,已分配的內(nèi)存為1.346MB,空閑的內(nèi)存為1MB薇宠,當(dāng)前Heap的使用率為57.37%偷办,對(duì)象的數(shù)量為24058個(gè)。
3.3 詳情視圖
詳細(xì)視圖展示了所有的數(shù)據(jù)類型的內(nèi)存情況澄港,表中列的信息如下所示椒涯。
|列 | 說(shuō)明 |
| :-------- :|: --------:|
| Type|數(shù)據(jù)類型 |
| Total Size|總共占用的內(nèi)存大小 |
| Smallest|將該數(shù)據(jù)類型的對(duì)象從小到大排列,排在第一個(gè)的對(duì)象所占用的內(nèi)存 |
| Largest|將該數(shù)據(jù)類型的對(duì)象從小到大排列回梧,排在最后一個(gè)的對(duì)象所占用的內(nèi)存 |
| Median|將該數(shù)據(jù)類型的對(duì)象從小到大排列废岂,排在中間的對(duì)象所占用的內(nèi)存 |
| Average|該數(shù)據(jù)類型的對(duì)象所占用內(nèi)存的平均值 |
除了列的信息,還有行信息:
|行 | 說(shuō)明 |
| :-------- :|: --------:|
| free|內(nèi)存碎片|
| data object|對(duì)象 |
| class object|類 |
| 1-byte array (byte[],boolean[])|1字節(jié)的數(shù)組對(duì)象 |
| 2-byte array (short[],char[])|2字節(jié)的數(shù)組對(duì)象 |
| 4-byte array (object[],int[],float[])|4字節(jié)的數(shù)組對(duì)象 |
| 6-byte array (long[],double[])|8字節(jié)的數(shù)組對(duì)象 |
| non-Java object|非Java對(duì)象 |
行信息中比較重要的是free漂辐,它與總覽視圖中的free的含義不同,它代表內(nèi)存碎片棕硫。當(dāng)新創(chuàng)建一個(gè)對(duì)象時(shí)髓涯,如果碎片內(nèi)存能容下該對(duì)象,則復(fù)用碎片內(nèi)存哈扮,否則就會(huì)從free空間(總覽視圖中的free)重新劃分內(nèi)存給這個(gè)新對(duì)象纬纪。free是判斷內(nèi)存碎片化程度的一個(gè)重要的指標(biāo)。
此外滑肉,1-byte array這一行的信息也很重要包各,因?yàn)閳D片是以byte[]的形式存儲(chǔ)在內(nèi)存中的,如果1-byte array一行的數(shù)據(jù)過(guò)大靶庙,則需要檢查圖片的內(nèi)存管理了问畅。
3.4 檢測(cè)內(nèi)存泄漏
Heap Dump也可以檢測(cè)內(nèi)存泄漏。在左邊Devices列表中選擇要查看的應(yīng)用程序進(jìn)程六荒,點(diǎn)擊Update Heap按鈕(裝有一半綠色液體的圓柱體)护姆,在右邊選擇Heap選項(xiàng),并點(diǎn)擊Cause GC按鈕掏击,就會(huì)開始顯示數(shù)據(jù)卵皂,如下圖所示。
這時(shí)data object的Total Size為270.266KB砚亭。接下來(lái)操作應(yīng)用灯变,這個(gè)應(yīng)用仍舊是在2.2小節(jié)所舉的內(nèi)存泄漏的例子,我反復(fù)的在MainActivity和SecondActivity跳轉(zhuǎn)了10次(點(diǎn)擊Button共20次)捅膘,數(shù)據(jù)顯示為:
data object的Total Size變?yōu)榱?68.172KB添祸。這時(shí)我點(diǎn)擊Cause GC按鈕,數(shù)據(jù)顯示為:
可以看到data object的Total Size變?yōu)榱?44.516KB寻仗,再點(diǎn)擊一次Cause GC按鈕:
Total Size變?yōu)榱?23.312KB膝捞,經(jīng)過(guò)兩次Cause GC的操作,Total Size的值從768.172KB變?yōu)榱?23.312KB,這是一個(gè)比較大的變化蔬咬,說(shuō)明在Cause GC操作之前有462.86KB(768.172KB-323.312KB)的內(nèi)存沒(méi)有被回收鲤遥,可能發(fā)生了內(nèi)存泄漏。
參考資料
Memory Monitor
Allocation Tracker
Android Monitor Basics
Android性能專項(xiàng)測(cè)試之Memory Monitor工具
《Android應(yīng)用性能優(yōu)化最佳實(shí)踐》
《Android群英傳 神兵利器》
《高性能Android應(yīng)用開發(fā)》