LeakCanary 原理分析

本文主要內(nèi)容

  • 1、Reference 簡介
  • 2、LeakCanary 使用
  • 3、LeakCanary 源碼分析

LeakCanary ,一種常見的內(nèi)存泄漏分析工具,它能分析出內(nèi)存泄漏點并以通知形式告訴使用者,使用也比較簡單,但功能強大。

筆者第一次見到 LeakCanary 時,并不清楚它的原理,了解到它只是一個開源工程并不是官方工具之后,覺得作者太牛逼了,內(nèi)存泄漏都可以檢測。今天我們一起來看 LeakCanary 的源碼,揭開它的神秘面紗。

1、Reference 簡介

java中存在四種引用,重新溫習(xí)一遍四種引用的用法及作用:

  • 強引用:最普遍的引用,聲明一個變量就是強引用,比如 obj ,當(dāng)它被置為null時,該對象可能會被 JVM 回收,因為還要看是否有其它強引用指向它。

    Object obj = new Object();
    
  • SoftReference,軟引用:當(dāng)內(nèi)存不夠用的時候,才會回收軟引用

  • WeakReference,弱引用: new出來的對象沒有強引用連接時,下一次GC時,就會回收該對象。

  • PhantomReference,虛引用 : 與要與ReferenceQueue配合使用,它的get()方法永遠(yuǎn)返回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);
        }
    }
}

看到這里,可能有同學(xué)會提問,pending 在哪被賦值的?怎么知道它就是要被回收的 Reference 呢?確實,這個問題我也沒有從源碼中查到,從網(wǎng)上找的資料來看,都說 pending 由 JVM 維護(hù)。Reference 有個成員變量next,它可以很輕松地變成一個鏈表,而pending 正是一個鏈表的頭節(jié)點,如果某個 Reference 將被回收,它將被 添加到pending 的鏈表當(dāng)中,如果它馬上要被回收了,ReferenceHandler 線程將它添加到 ReferenceQueue 中。

通過 Reference 的學(xué)習(xí),感覺 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)存可達(dá)性算法,即從一個根對象出發(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 對象,這是一個鏈?zhǔn)秸{(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 方法,我們別被這種鏈?zhǔn)秸{(diào)用弄暈了,這些不重要,別死摳細(xì)節(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 ,比較簡單,其實就是將之前鏈?zhǔn)秸{(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),當(dāng)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,當(dāng) activity 被回收時,KeyedWeakReference 對象會被添加到ReferenceQueue當(dāng)中。如果出現(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當(dā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.
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)存。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容