本文主要內(nèi)容
- 1、Reference 簡介
- 2、LeakCanary 使用
- 3丹鸿、LeakCanary 源碼分析
LeakCanary ,一種常見的內(nèi)存泄漏分析工具泣栈,它能分析出內(nèi)存泄漏點并以通知形式告訴使用者卜高,使用也比較簡單,但功能強大南片。
筆者第一次見到 LeakCanary 時掺涛,并不清楚它的原理,了解到它只是一個開源工程并不是官方工具之后疼进,覺得作者太牛逼了薪缆,內(nèi)存泄漏都可以檢測。今天我們一起來看 LeakCanary 的源碼伞广,揭開它的神秘面紗拣帽。
1、Reference 簡介
java中存在四種引用嚼锄,重新溫習一遍四種引用的用法及作用:
-
強引用:最普遍的引用减拭,聲明一個變量就是強引用,比如 obj 区丑,當它被置為null時拧粪,該對象可能會被 JVM 回收修陡,因為還要看是否有其它強引用指向它。
Object obj = new Object();
SoftReference可霎,軟引用:當內(nèi)存不夠用的時候魄鸦,才會回收軟引用
WeakReference,弱引用: new出來的對象沒有強引用連接時癣朗,下一次GC時拾因,就會回收該對象。
PhantomReference旷余,虛引用 : 與要與ReferenceQueue配合使用绢记,它的get()方法永遠返回null
SoftReference、WeakReference荣暮、PhantomReference等類都位于 java.lang.ref 包中庭惜,它們都有一個共同的父類,Reference 穗酥。
java.lang.ref包下主要都是reference相關(guān)的類护赊,主要包括:
- FinalReference: 代表強引用,使沒法直接使用砾跃。
- Finalizer:FinalReference的子類骏啰,主要處理finalize相關(guān)的工作
- PhantomReference: 虛引用
- Reference: 引用基類,abstract的
- ReferenceQueue: 引用軌跡隊列
- SoftReference:軟引用
- WeakedReference: 弱引用
下面抽高,闡述關(guān)于Reference 相關(guān)的一個結(jié)論:
Reference引用的對象被回收時判耕,Reference 對象將被添加到 ReferenceQueue中,前提是構(gòu)造 Reference 時翘骂,參數(shù)中有 ReferenceQueue壁熄。
如果要監(jiān)聽某個對象是否被回收,有什么辦法呢碳竟?
Object obj = new Object();
ReferenceQueue<Object> queue = new ReferenceQueue<>();
WeakedReference r = new WeakedReference(ojb, queue);
根據(jù)上面的結(jié)論草丧,如果 obj 對象被回收了,那么 queue 將添加 r莹桅,那么我們可以查找隊列昌执,如果有r,則證明 obj 對象被回收了诈泼,監(jiān)控完成懂拾。
查看下 Reference 的源碼,它里邊的關(guān)鍵代碼如下:
// 靜態(tài)變量,pending
private static Reference pending = null;
// 線程,不停地回收 pending
private static class ReferenceHandler extends Thread {
ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}
public void run() {
for (;;) {
Reference r;
synchronized (lock) {
if (pending != null) {
r = pending;
Reference rn = r.next;
pending = (rn == r) ? null : rn;
r.next = r;
} else {
try {
lock.wait();
} catch (InterruptedException x) { }
continue;
}
}
// Fast path for cleaners
if (r instanceof Cleaner) {
((Cleaner)r).clean();
continue;
}
// 將 r 添加到 ReferenceQueue 中
ReferenceQueue q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
}
}
}
看到這里穗泵,可能有同學會提問,pending 在哪被賦值的唐断?怎么知道它就是要被回收的 Reference 呢汁汗?確實,這個問題我也沒有從源碼中查到栗涂,從網(wǎng)上找的資料來看,都說 pending 由 JVM 維護祈争。Reference 有個成員變量next斤程,它可以很輕松地變成一個鏈表,而pending 正是一個鏈表的頭節(jié)點菩混,如果某個 Reference 將被回收忿墅,它將被 添加到pending 的鏈表當中,如果它馬上要被回收了沮峡,ReferenceHandler 線程將它添加到 ReferenceQueue 中疚脐。
通過 Reference 的學習,感覺 LeakCanary 最大的難題解決了邢疙,如何判定一個對象是否被回收了棍弄。
2、LeakCanary 使用
LeakCanary 使用非常簡單疟游,首先在你項目app下的build.gradle中配置:
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
// 可選呼畸,如果你使用支持庫的fragments的話
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'
}
然后在你的Application中配置:
public class WanAndroidApp extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// 1
return;
}
// 2
refWatcher = LeakCanary.install(this);
}
使用就是這么得簡單。
怎么判斷一個對象已死呢颁虐?內(nèi)存可達性算法蛮原,即從一個根對象出發(fā),如果無法尋到一條路徑指向該對象另绩,則對象已死儒陨。
假設(shè)是我們自己來設(shè)計 LeakCanary ,我們會怎么去設(shè)計呢笋籽?目前已經(jīng)可以監(jiān)控某個對象是否已經(jīng)被回收了蹦漠。我們也不可能去監(jiān)聽所有的對象吧,這樣不現(xiàn)實干签,肯定是去找特定對象來監(jiān)控津辩,在Android中,常見的內(nèi)存泄漏都會導(dǎo)致 activity 無法被回收容劳,activity 就是最特定的對象喘沿,所以,可以監(jiān)聽 activity竭贩。
LeakCanary 正是這樣設(shè)計的蚜印,它目前可以監(jiān)聽 activity 和 fragment。
3留量、LeakCanary 源碼分析
先看看 install 方法:
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
install 方法返回 RefWatcher 對象窄赋,這是一個鏈式調(diào)用哟冬,我們一步步地來看。
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
return new AndroidRefWatcherBuilder(context);
}
refWatcher 方法返回 AndroidRefWatcherBuilder 對象忆绰,從名字可知它是一個構(gòu)造器浩峡,builder,它的構(gòu)造函數(shù)也很簡單错敢,保存context
AndroidRefWatcherBuilder(@NonNull Context context) {
this.context = context.getApplicationContext();
}
繼續(xù)回到 install 方法翰灾,查看 listenerServiceClass 方法:
public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
@NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
return self();
}
AndroidRefWatcherBuilder 繼承自 RefWatcherBuilder ,上面的代碼就是為 AndroidRefWatcherBuilder 賦值一個成員變量稚茅,heapDumpListener 纸淮。繼續(xù)回到 install 方法
excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
這句話的意思是,排除各種已經(jīng)的不是內(nèi)存泄漏的情況亚享。其實為什么要在第一步中指出咽块,這是一個 Builder呢,我們常見的 builder 代碼里欺税,有各種各樣的賦值侈沪,但最關(guān)鍵的代碼往往只是它的 build 方法,我們別被這種鏈式調(diào)用弄暈了魄衅,這些不重要峭竣,別死摳細節(jié)不放,大概明白意思就行晃虫,接下來我們看最重要的方法:
public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
//調(diào)用build方法皆撩,構(gòu)建 RefWatcher
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
if (enableDisplayLeakActivity) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
if (watchActivities) {
//監(jiān)聽activity
ActivityRefWatcher.install(context, refWatcher);
}
//監(jiān)聽 fragment
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
build 方法返回 RefWatcher ,比較簡單哲银,其實就是將之前鏈式調(diào)用賦值的各個對象扛吞,賦值給 RefWatcher
public final RefWatcher build() {
if (isDisabled()) {
return RefWatcher.DISABLED;
}
if (heapDumpBuilder.excludedRefs == null) {
heapDumpBuilder.excludedRefs(defaultExcludedRefs());
}
HeapDump.Listener heapDumpListener = this.heapDumpListener;
if (heapDumpListener == null) {
heapDumpListener = defaultHeapDumpListener();
}
DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
debuggerControl = defaultDebuggerControl();
}
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
heapDumper = defaultHeapDumper();
}
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
watchExecutor = defaultWatchExecutor();
}
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
if (heapDumpBuilder.reachabilityInspectorClasses == null) {
heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
}
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
heapDumpBuilder);
}
最最關(guān)鍵的還得是 install 方法,本文中我們只分析 activity 的邏輯荆责,fragment 暫不分析滥比。
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
install 方法中只有三行,第一步做院,獲取 Application 對象盲泛,第二步,生成一個 ActivityRefWatcher 對象键耕,第三步寺滚,看方法名,貌似是注冊了一個監(jiān)聽屈雄,一個activity生命周期的監(jiān)聽村视。看看 lifecycleCallbacks 這個回調(diào)對象:
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
關(guān)鍵代碼終于出現(xiàn)酒奶,當activity 調(diào)用 onDestroyed 方法時蚁孔,會調(diào)用lifecycleCallbacks 中的方法奶赔,從而可以拿到 activity 的引用。結(jié)合之前 Reference 的分析杠氢,要監(jiān)聽對象是否被回收站刑,首先得拿到它的引用,現(xiàn)在 activity 的引用拿到了鼻百。繼續(xù)往下分析笛钝。
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
通過拿到的 activity 引用,構(gòu)造 KeyedWeakReference 對象愕宋,其實它繼承自 WeakReference ,它是一個弱引用结榄。構(gòu)建弱引用的同時中贝,在構(gòu)造函數(shù)中添加 ReferenceQueue,當 activity 被回收時臼朗,KeyedWeakReference 對象會被添加到ReferenceQueue當中邻寿。如果出現(xiàn)內(nèi)存泄漏,則 ReferenceQueue 找不到對應(yīng)的 KeyedWeakReference 對象视哑,那么就可以判斷發(fā)生內(nèi)存泄漏了绣否。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
ensureGoneAsync方法中,通過 watchExecutor 執(zhí)行一個 run 方法挡毅,watchExecutor 只會在主線程中執(zhí)行它蒜撮,繼續(xù)查看 ensureGone方法
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
//gone方法即是查看到 activity 已經(jīng)被回收,則返回 DONE跪呈,表示內(nèi)存無漏泄
if (gone(reference)) {
return DONE;
}
//調(diào)用 gc
gcTrigger.runGc();
removeWeaklyReachableReferences();
//調(diào)用gc之后段磨,再次檢查 ,如果 activity 還沒被回收耗绿,則是有內(nèi)存泄漏了
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
// 分析 heap 的prof文件苹支,找出內(nèi)存泄漏點
heapdumpListener.analyze(heapDump);
}
return DONE;
}
如果 activity 已經(jīng)被回收,那么 ReferenceQueue 中將添加指向它的 Reference误阻,這是一條大原則债蜜,一定要記住。在 watch 方法時究反,為每個 activity構(gòu)造對應(yīng)的Reference時寻定,還添加了一個key,并把key添加到一個set當中奴紧。
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.
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
遍歷 ReferenceQueue 中得到的Reference特姐,拿到 Reference,同時刪除 set 中對應(yīng)的key黍氮。所以唐含,set中不包含某個 key浅浮,則說明對應(yīng)的 activity已經(jīng)被回收,反之則是沒有回收捷枯。
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
gone方法正好驗證這點滚秩,如果gone返回為true,那么整個過程也結(jié)束了淮捆。如果gone返回為false郁油,則表明可能有內(nèi)存泄漏,所以執(zhí)行一次gc之后 攀痊,再次調(diào)用gone方法桐腌,查看是否有無泄漏。如果還有泄漏苟径,則分析生成的prof文件案站,找出關(guān)鍵的泄漏路徑。關(guān)于如何分析 prof 文件棘街,此處不展開了蟆盐,其實 LeakCanary 也是借用其它的開源庫來分析的。
最后總結(jié)下整個過程:
在一個Activity執(zhí)行完onDestroy()之后遭殉,將它放入WeakReference中石挂,然后將這個WeakReference類型的Activity對象與ReferenceQueque關(guān)聯(lián)。這時再從ReferenceQueque中查看是否有沒有該對象险污,如果沒有痹愚,執(zhí)行g(shù)c,再次查看蛔糯,還是沒有的話則判斷發(fā)生內(nèi)存泄露了里伯。最后用HAHA這個開源庫去分析dump之后的heap內(nèi)存。