版權(quán)聲明:本文為LooperJing原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明出處!
上篇說(shuō)了一些性能優(yōu)化的理論部分,主要是回顧一下,有了理論,小平同志又講了,實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn),對(duì)于內(nèi)存泄露的問(wèn)題,現(xiàn)在通過(guò)Android Studio自帶工具M(jìn)emory Monitor 檢測(cè)出來(lái)。性能優(yōu)化的重要性不需要在強(qiáng)調(diào),但是要強(qiáng)調(diào)一下,我并不是一個(gè)老司機(jī),嘿嘿!沒(méi)用過(guò)這個(gè)工具的,請(qǐng)睜大眼睛。如果你用過(guò),那么就不用在看這篇博客了。
先看一段會(huì)發(fā)生內(nèi)存泄露的代碼
public class UserManger {
private static UserManger instance;
private Context context;
private UserManger(Context context) {
this.context = context;
}
public static UserManger getInstance(Context context) {
if (instance == null) {
instance = new UserManger(context);
}
return instance;
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
UserManger userManger = UserManger.getInstance(this);
}
}
代碼很簡(jiǎn)單,就是一個(gè)單利模式泄露的場(chǎng)景,我們現(xiàn)在的關(guān)心的不是代碼本身,而是如何將代碼里面的內(nèi)存泄露給找出來(lái)。但是對(duì)于上面的代碼發(fā)生內(nèi)存泄露的原因還是有必要提一下。
上篇博客說(shuō)了,內(nèi)存泄漏產(chǎn)生的原因是:當(dāng)一個(gè)對(duì)象已經(jīng)不需要再使用了,本該被回收時(shí),而有另外一個(gè)正在使用的對(duì)象持有它的引用從而就導(dǎo)致,對(duì)象不能被回收。這種導(dǎo)致了本該被回收的對(duì)象不能被回收而停留在堆內(nèi)存中,就產(chǎn)生了內(nèi)存泄漏。
在上面的代碼中,發(fā)生泄露的不是UserManger,而是MainActivity,UserManger中有一個(gè)靜態(tài)成員instance,其生命周期和應(yīng)用程序的生命周期一致,當(dāng)退出應(yīng)用時(shí),才能被銷毀,但是當(dāng)GC準(zhǔn)備回收MainActivity時(shí),結(jié)果呢MainActivity的對(duì)象(this)在被UserManger所引用,UserManger本身又不能被干掉,所以就發(fā)生了內(nèi)存泄露。
Memory Monitor是Android Monitors中的一種,Monitors主要包括四種,Memory Monitor ,CPU Monitor ,NetWork Monitor, GPU Monitor ,今天介紹的是Memory Monitor ,其他的Monitor,在后面也準(zhǔn)備講。
-
Memory Monitor界面
- 圖中水平方向是時(shí)間軸,豎直方向是內(nèi)存的分配情況
- 圖中深藍(lán)色的區(qū)域,表示當(dāng)前正在使用中的內(nèi)存總量,淺藍(lán)色或者淺灰色區(qū)域,表示空閑內(nèi)存或者叫作未分配內(nèi)存。
- 左上角工具欄三個(gè)圓圈按鈕依次代表
GC按鈕 ,可以手動(dòng)GC,回收程序垃圾
內(nèi)存快照(Dump Java Heap) ,點(diǎn)擊可以生成一個(gè)文件(包名+日期+“.hprof”),可以記錄摸一個(gè)時(shí)間點(diǎn)內(nèi),程序內(nèi)存的情況
Allocation Traking ,點(diǎn)擊一次開(kāi)始, 再次點(diǎn)擊結(jié)束,也可以可以生成一個(gè)文件。
回到我們的程序,多點(diǎn)擊幾次GC,看一下這個(gè)應(yīng)用的內(nèi)存使用情況。
可以看到現(xiàn)在已經(jīng)分配的內(nèi)存有19.68M,我把手機(jī)旋轉(zhuǎn)一下,在看。
可以看到現(xiàn)在的內(nèi)存使用量是21.09M,還是一樣的界面,卻多了1.41M!!!這很關(guān)鍵。
接下來(lái),我們找一下,哪里發(fā)生了泄露。點(diǎn)擊Dump Java Heap,生成快照文件tool.test.memory.memoryleak_2016.11.13_21.38.hprof,Android Studio 自動(dòng)彈出HPROF Viewer來(lái)分析它。
現(xiàn)在介紹一下HPROF Viewer的用法
-
HPROF Viewer查看方式
左上角兩個(gè)紅框,是可選列表, 分別是用來(lái)選擇Heap區(qū)域, 和Class View的展示方式的.
Heap類型分為:
App Heap -- 當(dāng)前App使用的Heap
Image Heap -- 磁盤上當(dāng)前App的內(nèi)存映射拷貝
Zygote Heap -- Zygote進(jìn)程Heap(每個(gè)App進(jìn)程都是從Zygote孵化出來(lái)的, 這部分基本是framework中的通用的類的Heap)
Class List View -- 類列表方式
Package Tree View -- 根據(jù)包結(jié)構(gòu)的樹(shù)狀顯示
我通常點(diǎn)擊App heap下面的Classs Name把Heap中所有類按照字母順序排序,然后按照字母順序查找。
-
HPROF Viewer主要分ABC三大板塊
板塊A:這個(gè)應(yīng)用中所有類的名字
版塊B:左邊類的所有實(shí)例
板塊C:在選擇B中的實(shí)例后,這個(gè)實(shí)例的引用樹(shù)
- A板塊左上角列名解釋
列名 | 解釋 |
---|---|
Class Name | 類名,Heap中的所有Class |
Total Count | 內(nèi)存中該類這個(gè)對(duì)象總共的數(shù)量,有的在棧中,有的在堆中 |
Heap Count | 堆內(nèi)存中這個(gè)類 對(duì)象的個(gè)數(shù) |
Sizeof | 每個(gè)該實(shí)例占用的內(nèi)存大小 |
Shallow Size | 所有該類的實(shí)例占用的內(nèi)存大小 |
Retained Size | 所有該類對(duì)象被釋放掉,會(huì)釋放多少內(nèi)存 |
- B板塊右上角上角列名解釋
列名 | 解釋 |
---|---|
Instance | 該類的實(shí)例 |
Depth | 深度, 從任一GC Root點(diǎn)到該實(shí)例的最短跳數(shù) |
Dominating Size | 該實(shí)例可支配的內(nèi)存大小 |
B板塊右上角有個(gè)"的按鈕, 點(diǎn)擊會(huì)進(jìn)入HPROF Analyzer的hprof的分析界面:
"
在這個(gè)界面中可以直接把內(nèi)存泄露可能的類找出來(lái)。
下面分析一下MainActivity的泄露情況
- 一個(gè)Activity應(yīng)該只有一個(gè)實(shí)例,但是從A區(qū)域來(lái)看 total count的值為2,heap count的值也為2,說(shuō)明有一個(gè)是多余的。
- 在B區(qū)域中可以看見(jiàn)兩個(gè)MainActivity的實(shí)例,點(diǎn)擊一個(gè)看他的引用樹(shù)情況
- 在C區(qū)域中可以看到MainActivity的實(shí)例Context被UserManger的 instance引用了,引用深度為1.
- 在Analyzer Tasks 區(qū)域中,直接告訴你Leaked Activities,MainActivity包含其中
多方面的證據(jù)表明MainActivity發(fā)生了內(nèi)存泄露
解決方案
public class UserManger {
private static UserManger instance;
private Context context;
private UserManger(Context context) {
this.context = context;
}
public static UserManger getInstance(Context context) {
if (instance == null) {
if(context!=null){
instance = new UserManger(context.getApplicationContext());
}
}
return instance;
}
}
不要用Activity的Context,因?yàn)锳ctivity隨時(shí)可能被回收,我們用Application的Context,Application的Context的生命周期是整個(gè)應(yīng)用,不回收也沒(méi)有關(guān)系。
Memory Monitor獲得內(nèi)存的動(dòng)態(tài)視圖,Heap Viewer顯示堆內(nèi)存中存儲(chǔ)了什么,可惜Heap Viewer不能顯示你的數(shù)據(jù)具體分配在代碼的何處,如果還不過(guò)癮,想知道具體是哪些代碼使用了內(nèi)存,還有一個(gè)功能是Allocation Tracker,用來(lái)內(nèi)存分配追蹤。在內(nèi)存圖中點(diǎn)擊途中標(biāo)紅的部分,啟動(dòng)追蹤,再次點(diǎn)擊就是停止追蹤,隨后自動(dòng)生成一個(gè)alloc結(jié)尾的文件,這個(gè)文件就記錄了這次追蹤到的所有數(shù)據(jù),然后會(huì)在右上角打開(kāi)一個(gè)數(shù)據(jù)面板
Allocation Tracker啟動(dòng)追蹤
Allocation Tracker查看方式
有兩種查看方式,默認(rèn)是Group by Method方式
- Group by Method:用方法來(lái)分類我們的內(nèi)存分配
- Group by Allocator:用內(nèi)存分配器來(lái)分類我們的內(nèi)存分配
從上圖可以看出,首先以線程對(duì)象分類,Size是內(nèi)存大小,Count是分配了多少次內(nèi)存,點(diǎn)擊一下線程就會(huì)查看每個(gè)線程里所有分配內(nèi)存的方法
-
Group by Method方式
每個(gè)線程里所有分配內(nèi)存的方法.png
OK,-Memory Monitor -
** Group by Allocator方式**
EY%HY_B74%BUE22C6$G~CTP.png
右鍵可以直接跳到源碼
- 扇形統(tǒng)計(jì)圖
點(diǎn)擊統(tǒng)計(jì)圖按鈕,會(huì)生成上圖,扇形統(tǒng)計(jì)圖是以圓心為起點(diǎn),最外層是其內(nèi)存實(shí)際分配的對(duì)象,每一個(gè)同心圓可能被分割成多個(gè)部分,代表了其不同的子孫,每一個(gè)同心圓代表他的一個(gè)后代,每個(gè)分割的部分代表了某一帶人有多人,你雙擊某個(gè)同心圓中某個(gè)分割的部分,會(huì)變成以你點(diǎn)擊的那一代為圓心再向外展開(kāi)。
除了扇形圖,還有柱狀圖可選擇,可以自己操作,OK,Memory Monitor到此結(jié)束,下一篇性能優(yōu)化部分博客仍然是檢測(cè)內(nèi)存泄露,明天上班,晚安!