主目錄見:Android高級進(jìn)階知識(這是總目錄索引)
?性能優(yōu)化很重要的一個(gè)環(huán)節(jié)就是檢測有沒有內(nèi)存泄漏沦泌,以前我們內(nèi)存泄漏會(huì)借助MAT,androidstudio Monitor(androidstudio 3.0改成Android profiler)等工具孝情,檢測過程會(huì)比較麻煩一點(diǎn)龄广,而LeakCanary作為一個(gè)自動(dòng)內(nèi)存泄漏工具出現(xiàn)帅腌,應(yīng)該說它的簡單易用給我們省了好多工作量绿满,提升了我們的代碼質(zhì)量亿胸。也許大家會(huì)說汹想,java不是有自動(dòng)垃圾回收機(jī)制嗎追葡?但是其實(shí)一些持有外部引用超過他應(yīng)有的生命周期的話腺律,那么這個(gè)時(shí)候垃圾回收機(jī)制是不會(huì)去回收的奕短,這時(shí)候就會(huì)出現(xiàn)不可預(yù)期地內(nèi)存暴走。
一.目標(biāo)
今天的目標(biāo)就是為了能明白LeakCanary大致的原理過程匀钧,然后大家能更放心使用翎碑,具體目標(biāo)如下:
1.明白LeakCanary版本的差異;
2.LeakCanary的內(nèi)存檢測思路之斯。
二.源碼分析
具體的使用我們就不在這邊說了日杈,因?yàn)間ithub上面都有,而且現(xiàn)在4.0版本以上的使用變得簡單很多佑刷,我們來在代碼中的使用:
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code...
}
}
很簡單莉擒,但是其實(shí)在4.0以上其實(shí)才變得如此易用,這是為什么呢瘫絮?這跟Application.ActivityLifecycleCallbacks
這個(gè)方法有關(guān)(這個(gè)接口在Android 4.0才有)涨冀,這個(gè)方法其實(shí)我們在前面的換膚框架實(shí)現(xiàn)解析(二)這篇文章有講解過,這個(gè)方法可以監(jiān)測到Activity
的各個(gè)生命周期麦萤,然后在LeakCanary
就可以在onActivityDestroyed
方法中為所有的Activity
調(diào)用refWatcher.watch(activity)
鹿鳖。
總體流程
在分析LeakCanary
之前,我們先來明確一下總體流程壮莹,檢測主要分為三個(gè)步驟:
- 1.分析是否有可疑的泄漏對象栓辜,主要是通過弱引用機(jī)制來檢測;
- 2.如果第一步發(fā)現(xiàn)了可疑泄漏對象垛孔,那么就會(huì)
dump
內(nèi)存快照,然后分析.hprof
文件確定是否真的泄漏了施敢。 - 3.展示分析的結(jié)果周荐。
1.分析可疑泄漏對象
我們知道,因?yàn)槲覀儜?yīng)用了Application.ActivityLifecycleCallbacks(Activity)方法僵娃,所以我們程序會(huì)在ActivityRefWatcher
類中注冊一個(gè)lifecycleCallbacks
對象來監(jiān)測Activity
的生命周期概作,LeakCanary
就是在onActivityDestroyed
里面調(diào)用了refWatcher.watch(activity)
方法,這里的refWatcher
是在前面build()
方法中初始化的默怨,我們現(xiàn)在直接看RefWatcher的watch()方法:
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
這里的watchedReference就是我們的每個(gè)activity對象讯榕,我們看到這個(gè)方法又調(diào)用了內(nèi)部兩個(gè)參數(shù)的watch方法:
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
//對一個(gè)引用產(chǎn)生一個(gè)唯一的Key
String key = UUID.randomUUID().toString();
//放到key集合中
retainedKeys.add(key);
//將要監(jiān)測的對象添加一個(gè)弱引用
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
//在子線程中分析這個(gè)弱引用
ensureGoneAsync(watchStartNanoTime, reference);
}
從上面的代碼可以知道,其實(shí)就是給監(jiān)測對象添加一個(gè)弱引用匙睹,然后使用ReferenceQueue
來監(jiān)測它的可達(dá)性的改變愚屁,其中key是一個(gè)唯一的uuid,而最后的ensureGoneAsync()
是我們主要的分析方法了痕檬,我們來看看:
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
我們看到程序中watchExecutor
是個(gè)什么東西呢霎槐?LeakCanary為我們實(shí)現(xiàn)了AndroidWatchExecutor
,這里面利用HandlerThread
的機(jī)制梦谜,在子線程中來處理分析這個(gè)邏輯(如果這個(gè)地方不懂丘跌,推薦看HandlerThread源碼分析)袭景,我們主要的分析方法是在ensureGone中,我們直接來看:
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//刪除所有已經(jīng)在ReferenceQueue中的弱引用
removeWeaklyReachableReferences();
//如果當(dāng)前處于調(diào)試狀態(tài)則返回
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
//如果當(dāng)前的對象只有弱引用了闭树,那么說明不會(huì)泄露
if (gone(reference)) {
return DONE;
}
//如果當(dāng)前的對象還沒有改變?nèi)蹩蛇_(dá)狀態(tài)耸棒,則我們手動(dòng)調(diào)用GC
gcTrigger.runGc();
//再次刪除,確認(rèn)對象是不是已經(jīng)在ReferenceQueue中
removeWeaklyReachableReferences();
//如果當(dāng)前對象還沒有在ReferenceQueue报辱,說明可能泄露了与殃,則dump內(nèi)存快照
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//我們開始dump內(nèi)存快照
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
這里我們看到,我們?yōu)樯蹲詈筮€有dump
內(nèi)存快照捏肢,然后進(jìn)行分析.hprof
文件呢重斑,其實(shí)我們這里的GC只是建議虛擬機(jī)說進(jìn)行一次內(nèi)存回收,但是最終要不要進(jìn)行內(nèi)存回收是JVM說了算烁焙,如果這里建議沒被通過的時(shí)候宵蕉,那么我們的可達(dá)性就不會(huì)發(fā)生改變,我們就需要第二個(gè)步驟dump
內(nèi)存快照來分析辩棒。
2.dump內(nèi)存快照
我們看到程序的最后調(diào)用了heapdumpListener
的analyze
方法狼忱,那么這里的heapdumpListener是什么呢?這里要從LeakCanary
類中的install()
方法看起:
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
我們這里有個(gè)方法listenerServiceClass一睁,這個(gè)方法我們跟進(jìn)去看下:
public AndroidRefWatcherBuilder listenerServiceClass(
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
從這里我們可以看到我們的heapdumpListener
其實(shí)就是我們的ServiceHeapDumpListener
類對象钻弄,所以我們看到這個(gè)類的analyze
方法:
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
HeapAnalyzerService
是個(gè)IntentService
的子類(同樣的,不懂IntentService的話推薦IntentService源碼分析)者吁,所以我們的主要方法是在onHandIntent方法中分析的:
@Override protected void onHandleIntent(Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
我們看到程序new了一個(gè)HeapAnalyzer
對象窘俺,這個(gè)類主要負(fù)責(zé)分析hprof
文件的。然后程序會(huì)調(diào)用HeapAnalyzer
的checkForLeak
方法:
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
long analysisStartNanoTime = System.nanoTime();
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
//加載hprof文件
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
//解析
Snapshot snapshot = parser.parse();
//精簡gcroots,把重復(fù)的路徑刪除复凳,重新封裝成不重復(fù)的路徑的容器
deduplicateGcRoots(snapshot);
//找到泄漏對象的引用
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) {
return noLeak(since(analysisStartNanoTime));
}
//查找從這個(gè)對象的引用到GC ROOT的最短路徑
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
上面的代碼邏輯應(yīng)該算是比較簡單瘤泪,具體細(xì)節(jié)大家也不需要硬摳,我們知道育八,我們上面的代碼主要就是為了尋找到hprof文件中泄漏對象的引用路徑(泄漏對象到gcroot的最短路徑)对途,如果能找到說明我們的對象確實(shí)泄漏了,最后會(huì)調(diào)用AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result)
將發(fā)送出去髓棋。
3.泄漏結(jié)果展示
泄漏結(jié)果主要是在DisplayLeakService
類中實(shí)現(xiàn)的实檀,實(shí)現(xiàn)方法也不是很麻煩,大家可以自行查看按声,以為不在于主流程中膳犹,我們暫時(shí)就不講了。
總結(jié):我們知道我們android系統(tǒng)中可能自身存在一些泄漏情況儒喊,所以我們LeakCanary提供了AndroidExcludedRefs
類來進(jìn)行排除監(jiān)測镣奋,這樣我們不需要在乎Framework層本身的泄漏問題。現(xiàn)在LeakCanary的使用越來越多了怀愧,希望我們也能適當(dāng)在代碼中引入來檢測自己寫的代碼是否有泄漏的風(fēng)險(xiǎn)侨颈,進(jìn)而提升我們的代碼質(zhì)量余赢,當(dāng)然我們平時(shí)也要關(guān)注一些常見的內(nèi)存泄漏情況,我們可以參考MAT內(nèi)存泄漏分析(一)和MAT內(nèi)存泄漏分析(二)哈垢,最后祝大家性能優(yōu)化之路愉快妻柒。