
引言
前文內(nèi)存分析工具集中介紹了一系列的內(nèi)存分析工具及其基本使用, 諸如Memory Monitor, HPROF Viewer, MAT等等. 實(shí)際上了解了工具的使用, 我們就已經(jīng)掌握了如何分析內(nèi)存問(wèn)題了.
為了能對(duì)工具的使用更加深入, 本篇將一個(gè)代碼片段為例, 從時(shí)序的角度講解下如何使用這些工具來(lái)分析一個(gè)內(nèi)存泄露.
系列文:
1.GC那些事兒
2.Android的內(nèi)存管理
3.內(nèi)存分析工具
4.內(nèi)存泄露實(shí)例分析
1, 例子
假設(shè)有一個(gè)單例的ListenerManager, 可以add / remove Listener, 有一個(gè)Activity, 實(shí)現(xiàn)了該listener, 且這個(gè)Activity中持有大對(duì)象BigObject, BigObject中包含一個(gè)大的字符串?dāng)?shù)組和一個(gè)Bitmap List.
代碼片段如下:
ListenerManager
public class ListenerManager {
private static ListenerManager sInstance;
private ListenerManager() {}
private List<SampleListener> listeners = new ArrayList<>();
public static ListenerManager getInstance() {
if (sInstance == null) {
sInstance = new ListenerManager();
}
return sInstance;
}
public void addListener(SampleListener listener) {
listeners.add(listener);
}
public void removeListener(SampleListener listener) {
listeners.remove(listener);
}
}
MemoryLeakActivity
public class MemoryLeakActivity extends AppCompatActivity implements SampleListener {
private BigObject mBigObject = new BigObject();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memory_leak);
ListenerManager.getInstance().addListener(this);
}
@Override
public void doSomething() {
}
}
具體代碼參看Github.
2, 使用Android Studio的自帶工具來(lái)分析
根據(jù)前文的工具介紹, Android Studio自帶了Memory Monitor, HPROF Viewer & Analyzer來(lái)分析內(nèi)存使用及內(nèi)存問(wèn)題.
2.1 查看Memory使用, 并導(dǎo)出hprof文件
啟動(dòng)我們要檢測(cè)的Activity(MemoryLeakActivity), 然后退出, 在monitor中查看內(nèi)存變化:

2.2 在HPROF Viewer界面, 開(kāi)始分析

第一步
點(diǎn)擊"Analyzer Tasks"視圖中的啟動(dòng)按鈕, 啟動(dòng)分析
第二步
查看"Analysis Result"中的分析結(jié)果, 點(diǎn)擊"Leaked Activityes"中的具體實(shí)例, 該實(shí)例的引用關(guān)系將會(huì)展示在"Reference Tree"視圖中.
第三步
根據(jù)"Reference Tree"視圖中的引用關(guān)系找到是誰(shuí)讓這個(gè)leak的activity活著的, 也就是誰(shuí)Dominate這個(gè)activity對(duì)象.
此例中, 比較簡(jiǎn)單, 可以很清晰看到是ListenerManager的靜態(tài)單例sInstance最終支配了MemoryLeakActivity. sIntance連接到GC Roots, 故而導(dǎo)致MemoryLeakActivity GC Roots可達(dá), 無(wú)法被回收.
關(guān)于Dominate, GC Roots, GC Roots可達(dá), 活對(duì)象等概念, 請(qǐng)結(jié)合前兩篇的理論文1, 2觀看.
2.3 使用Heap Viewer查看內(nèi)存消耗
上述步驟, 可以讓我們快速定位可能的內(nèi)存泄露. 當(dāng)然, 內(nèi)存問(wèn)題, 除了內(nèi)存泄露, 還有內(nèi)存消耗過(guò)大. 我們可以在Heap Viewer中查看分析內(nèi)存的消耗點(diǎn), 如下:

3, MAT讓我們看的更多
就單純的分析Android App的內(nèi)存使用和內(nèi)存泄露來(lái)說(shuō), 個(gè)人覺(jué)得Android Studio自帶的工具已經(jīng)足夠好了, 而且再持續(xù)變得更好, 也更便于Android的開(kāi)發(fā)人員去理解. 故而其實(shí)一開(kāi)始在Android性能分析工具一文中, 我就沒(méi)有詳細(xì)去提MAT. 相對(duì)與Android Studio中的Memory Monitor, HPROF工具來(lái)說(shuō), MAT的使用顯得更加生澀, 難以理解.
關(guān)于MAT的幫助文檔, 個(gè)人翻譯了一份, 需要的同學(xué)戳這里.
當(dāng)然, 如果我們想對(duì)內(nèi)存的使用相關(guān)知識(shí)了解得更多, 還是有必要了解下MAT的...
下面我們以幾個(gè)角度來(lái)了解下MAT的基本使用:
再次:
Android Studio導(dǎo)出的hprof文件需要轉(zhuǎn)換下才可以在MAT中使用.
$ hprof-conv com.anly.samples_2016.10.31_15.07.hprof mat.hprof
3.1 Histogram視圖定位內(nèi)存消耗

MAT中很多視圖的第一行, 都可以輸入正則, 來(lái)匹配我們關(guān)注的對(duì)象實(shí)例.
3.2 Dominate Tree視圖查看支配關(guān)系

3.3 使用OQL查詢(xún)相關(guān)對(duì)象
對(duì)于Android App開(kāi)發(fā)來(lái)說(shuō), 大部分的內(nèi)存問(wèn)題都跟四大組件, 尤其是Activity相關(guān), 故而我們會(huì)想查出所有Activity實(shí)例的內(nèi)存占用情況, 可以使用OQL來(lái)查詢(xún):

具體OQL語(yǔ)法看這里.
3.4 GC路徑定位問(wèn)題
上面幾個(gè)視圖都可以讓我們很快速的找到內(nèi)存的消耗點(diǎn), 接下來(lái)我們要分析的就是為何這些個(gè)大對(duì)象沒(méi)有被回收.
根據(jù)第一彈:GC那些事兒所言, 對(duì)象沒(méi)有被回收是因?yàn)樗械紾C Roots的可達(dá)路徑. 那么我們就來(lái)分析下這條路徑(Path to GC Roots), 看看是誰(shuí)在這條路中"搭橋".
如下, 進(jìn)入該對(duì)象的"path2gc"視圖:


同樣, 與HPROF Analyzer異曲同工, 找出了是ListenerManager的靜態(tài)實(shí)例導(dǎo)致了MemoryLeakActivity無(wú)法回收.
4, LeakCanary讓內(nèi)存泄露無(wú)處可藏
大道至簡(jiǎn), 程序員都應(yīng)該"懶", 故而我們都希望有更方便快捷的方式讓我們發(fā)現(xiàn)內(nèi)存泄露. 偉大的square發(fā)揮了這一優(yōu)良傳統(tǒng), LeakCanary面世.
4.1 加入LeakCanary
app的build.gradle中加入:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
}
Application中加入:
public class SampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
4.2 操作要檢測(cè)的界面, 查看結(jié)果
當(dāng)發(fā)生可疑內(nèi)存泄露時(shí), 會(huì)在桌面生成一個(gè)"Leaks"的圖標(biāo), 點(diǎn)擊進(jìn)去可以看到內(nèi)存泄露的疑點(diǎn)報(bào)告:

可以看到, 結(jié)果與前二者的分析結(jié)果"驚人"一致, 不一致就出事兒了, :)
足夠方便且直觀吧, 趕快用起來(lái)吧.
當(dāng)然, 內(nèi)存問(wèn)題不僅僅是內(nèi)存泄露, 還有內(nèi)存占用過(guò)多等, 這時(shí)我們就需要借助前兩種工具了.
結(jié)語(yǔ)
綜上, 建議LeakCanary集成作為App的必選項(xiàng), 大多數(shù)情況下我們可以用LeakCanary結(jié)合Android Studio自帶的工具分析內(nèi)存問(wèn)題.
如果有精力, 還是建議深入了解下MAT, 能讓我們更深入了解GC的機(jī)制, 相關(guān)概念, MAT還有很多更高端的功能值得我們探索, 例如Heap比較等.
其實(shí), 內(nèi)存問(wèn)題的分析, 無(wú)外乎分析對(duì)象的內(nèi)存占用(Retained Size), 找出Retained Size大的對(duì)象, 找到其直接支配(Immediate Dominator), 跟蹤其GC可達(dá)路徑(Path to GC Roots), 從而找到是誰(shuí)讓這個(gè)大對(duì)象活著. 找到問(wèn)題癥結(jié), 對(duì)癥下藥.