LeakCanary
一個內(nèi)存泄漏檢測庫,適用于Android和Java環(huán)境
“A small leak will sink a great ship.” - Benjamin Franklin
小漏不補沉大船
使用效果如下圖:

接入步驟如下:
在你項目對應(yīng) build.gradle添加:
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
}
在你項目對應(yīng) Application 添加初始化:
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...
}
}
接入完成! LeakCanary在發(fā)現(xiàn)內(nèi)存泄漏的時候會彈出通直欄以及創(chuàng)建一個快捷方式,每個泄漏對應(yīng)一個快捷方式,快捷方式圖標(biāo)圖標(biāo)如下:有問題請點擊!
LeakCanray實現(xiàn)原理
基礎(chǔ)概念
ActivityLifecycleCallbacks
public interface ActivityLifecycleCallbacks {
void onActivityCreated(Activity activity, Bundle savedInstanceState);
void onActivityStarted(Activity activity);
void onActivityResumed(Activity activity);
void onActivityPaused(Activity activity);
void onActivityStopped(Activity activity);
void onActivitySaveInstanceState(Activity activity, Bundle outState);
void onActivityDestroyed(Activity activity);
}
Application通過此接口提供了一套回調(diào)方法,用于讓開發(fā)者對Activity的生命周期事件進行集中處理,可用于替換runningProcess或者runningTasks,判斷 app 是否在后臺運行
Android 4.0 (API Level 14) 引入
Java對象的強、軟、弱和虛引用
強引用(StrongReference)
強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。當(dāng)內(nèi)存空間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內(nèi)存不足的問題。
軟引用(SoftReference)
如果一個對象只具有軟引用,則內(nèi)存空間足夠,垃圾回收器就不會回收它;如果內(nèi)存空間不足了,就會回收這些對象的內(nèi)存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現(xiàn)內(nèi)存敏感的高速緩存(下文給出示例)。軟引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關(guān)聯(lián)的引用隊列中。
弱引用(WeakReference)
弱引用與軟引用的區(qū)別在于:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當(dāng)前內(nèi)存空間足夠與否,都會回收它的內(nèi)存。不過,由于垃圾回收器是一個優(yōu)先級很低的線程,因此不一定會很快發(fā)現(xiàn)那些只具有弱引用的對象。弱引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關(guān)聯(lián)的引用隊列中。
虛引用(PhantomReference)
“虛引用”顧名思義,就是形同虛設(shè),與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。虛引用主要用來跟蹤對象被垃圾回收器回收的活動。虛引用與軟引用和弱引用的一個區(qū)別在于:虛引用必須和引用隊列 (ReferenceQueue)聯(lián)合使用。當(dāng)垃圾回收器準(zhǔn)備回收一個對象時,如果發(fā)現(xiàn)它還有虛引用,就會在回收對象的內(nèi)存之前,把這個虛引用加入到與之 關(guān)聯(lián)的引用隊列中。
工作機制
在LeakCannary檢測泄露主要分三步:
1.關(guān)聯(lián)需要監(jiān)聽的對象 leakcanary-android
2.檢測監(jiān)聽對象是否存在泄露嫌疑,如果存在嫌疑,就dump出內(nèi)存快照到*.hprof文件 leakcanary-watcher
3.通過分析*.hprof文件確認是否真正泄露,泄露了就保存并展示結(jié)果 leakcanary-analyzer
關(guān)聯(lián)監(jiān)聽對象關(guān)鍵類ActivityRefWatcher
默認情況下,我們監(jiān)聽的是項目里的Activity.
利用ActivityLifecycleCallbacks的監(jiān)聽所有Activity生命周期,在Activity被銷毀時進行泄露檢測
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override public void onActivityStarted(Activity activity) {
}
@Override public void onActivityResumed(Activity activity) {
}
@Override public void onActivityPaused(Activity activity) {
}
@Override public void onActivityStopped(Activity activity) {
}
@Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override public void onActivityDestroyed(Activity activity) {
// 最關(guān)鍵的,activity被銷毀時,進行檢查
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
我們也可以使用RefWatcher關(guān)聯(lián)其他任何我們想要監(jiān)聽的對象:
// 使用 RefWatcher 監(jiān)控 Fragment:
public abstract class BaseFragment extends Fragment {
@Override public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
refWatcher.watch(this); // watch傳入的是Object對象
}
}
確認是否進行分析,關(guān)鍵類RefWatcher
我們知道弱引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關(guān)聯(lián)的引用隊列中。
LeakCanary利用弱引用這個特性,將要監(jiān)聽的對象和ReferenceQueue隊列聯(lián)合使用,如果對象被垃圾系統(tǒng)回收,就會放到隊列里,就可以利用這個隊列找出沒有被回收的對象,沒有被回收的則懷疑可能發(fā)生了內(nèi)存泄露,如下面的removeWeaklyReachableReferences()
// 這個方法執(zhí)行完,retainedKeys列表里就剩下沒有被系統(tǒng)回收的
private void removeWeaklyReachableReferences() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
// queue里裝的都算被垃圾系統(tǒng)回收的
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key); // 刪除已經(jīng)被系統(tǒng)回收
}
}
下面看完整的流程,主要在ensureGone()里
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// 1.刪除已經(jīng)被系統(tǒng)回收的對象
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// 2.判斷沒被回收的列表retainedKeys是否有reference對象
if (gone(reference)) {
return DONE;
}
// 3.調(diào)用gc
gcTrigger.runGc();
// 4.再次刪除已經(jīng)被系統(tǒng)回收的對象
removeWeaklyReachableReferences();
if (!gone(reference)) {
// 5.retainedKeys列表里存在reference對象,存在泄漏嫌疑
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// 6.dump出內(nèi)存快照到*.hprof文件
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
// 7.觸發(fā)對.hprof文件進行分析
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
分析*.hprof文件,找到泄露對象,并展示
ServiceHeapDumpListener實現(xiàn)了HeapDump.Listener接口,當(dāng)RefWatcher發(fā)現(xiàn)可疑引用的之后,它將dump出來的*.hprof文件通過ServiceHeapDumpListener傳遞到HeapAnalyzerService
HeapAnalyzerService它主要是通過HeapAnalyzer.checkForLeak分析對象的引用,計算出到GC root的最短強引用路徑。然后將分析結(jié)果傳遞給DisplayLeakService
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
long analysisStartNanoTime = System.nanoTime();
// 判斷*.hprof是否存在
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
// 利用HAHA(基于MAT的堆棧解析庫)將之前dump出來的內(nèi)存文件解析成Snapshot對象
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
Snapshot snapshot = parser.parse();
deduplicateGcRoots(snapshot);
// 找到泄露對象,LeakCanary通過被泄漏對象的弱引用來在Snapshot中定位它。
// 如果一個對象被泄漏,一定也可以在內(nèi)存中找到這個對象的弱引用,再通過弱引用對象的referent就可以直接定位被泄漏對象
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));
}
// 計算出一條有效的到被泄漏對象的最短的引用
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
DisplayLeakService繼承了AbstractAnalysisResultService。它主要是用來處理分析結(jié)果,將結(jié)果寫入文件,然后在通知欄報警
// listenerClassName對應(yīng)的就是DisplayLeakService
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
下面是以上步驟的時序圖


其他說明
LeakCanary提供了ExcludedRefs來靈活控制是否需要將一些對象排除在考慮之外,因為在Android Framework,手機廠商rom自身也存在一些內(nèi)存泄漏,對于開發(fā)者來說這些泄漏是我們無能為力的,所以在AndroidExcludedRefs中定義了很多排除考慮的類
未完待續(xù)。。。
風(fēng)格:
1、所有接口內(nèi)部都有個一個默認的實現(xiàn)
2、final類
3、沒有util,Preconditions,AndroidDebuggerControl
4、import方式import static com.squareup.leakcanary.AnalysisResult.leakDetected
參考文章
http://www.itdecent.cn/p/5032c52c6b0a
http://coolpers.github.io/leakcanary%7Cmat/2015/06/04/LeakCanary-Brief.html