LeakCanary 原理分析

LeakCanary 是Android開發(fā)中常用的內(nèi)存泄漏檢測工具。
我們以Activity的監(jiān)控為例,分析下是如何做泄漏分析的。

Activity監(jiān)控

首先看LeakCanary初始化時做的操作:

  /**
   * Creates a {@link RefWatcher} that works out of the box, and starts watching activity
   * references (on ICS+).
   */
  public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }

buildAndInstall 方法里面啟動了ActivityRefWatcher:

/**
   * Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
   */
  public RefWatcher buildAndInstall() {
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      LeakCanary.enableDisplayLeakActivity(context);
      ActivityRefWatcher.install((Application) context, refWatcher);
    }
    return refWatcher;
  }

ActivityRefWatcher里面有對Activity生命周期的監(jiān)控:

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) {
          ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
      };
      
private final RefWatcher refWatcher;      
void onActivityDestroyed(Activity activity) {
    refWatcher.watch(activity);
  }

所以RefWatcher里面是對象引用監(jiān)控的核心代碼。下面我們來看下RefWarcher里面具體是如何做監(jiān)控的。

RefWatcher 如何工作

RefWatcher屬于LeakCanary 的leakcanary-watcher包下,是Java通用而非Android特有。

RefWarcher 提供watch方法,在對象被回收之后調(diào)用RefWatch的watch方法即可監(jiān)控對象是否發(fā)生了泄漏。

private final ReferenceQueue<Object> queue;
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);
  }
補充知識: ReferenceQueue

Reference queues, to which registered reference objects are appended by the garbage collector after the appropriate reachability changes are detected.

可以簡單的理解為,創(chuàng)建對象的WeakReference時,可以傳入一個ReferenceQueue,當對象變?yōu)閮H弱引用可達時,會將WeakReference的包裝對象放入這個隊列??梢酝ㄟ^檢查ReferenceQueue隊列中是否存在WeakReference這個包裝對象來判斷一個實例是否被回收。

LeakCanary 通過以下流程判斷是否發(fā)生內(nèi)存泄漏:

RefWatch.java 中 ensureGone 的流程

HAHA分析Dump的結(jié)果會通過回調(diào)的形式返回??梢缘玫叫孤┑膶ο蟮囊寐窂?。
PS.
HAHA(Headless Android Heap Analyzer)是Square出的Dump分析工具。

檢測的時機

RefWatcher 的 watch 方法會異步做對象是否被回收的檢測。

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }

可以看到,其實調(diào)用是通過WatchExecutor watchExecutor來調(diào)用的。我們關(guān)注下WatchExecutor的Android實現(xiàn)類AndroidWatchExecutor:

@Override public void execute(Retryable retryable) {
    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
      waitForIdle(retryable, 0);
    } else {
      postWaitForIdle(retryable, 0);
    }
  }
void waitForIdle(final Retryable retryable, final int failedAttempts) {
    // This needs to be called from the main thread.
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override public boolean queueIdle() {
        postToBackgroundWithDelay(retryable, failedAttempts);
        return false;
      }
    });
  }

可以看到都是在主線程調(diào)用的 Looper.myQueue().addIdleHandler()方法。

補充知識2 MessageQueue.addIdleHandler
   /**
     * Callback interface for discovering when a thread is going to block
     * waiting for more messages.
     */
    public static interface IdleHandler {
        /**
         * Called when the message queue has run out of messages and will now
         * wait for more.  Return true to keep your idle handler active, false
         * to have it removed.  This may be called if there are still messages
         * pending in the queue, but they are all scheduled to be dispatched
         * after the current time.
         */
        boolean queueIdle();
    }

MessageQueue.addIdleHandler 可以簡單理解為MQ提供的一個接口,允許MQ在隊列空閑的時候做些事情。比如在空閑的時候做個GC啊。

補充知識3 如何手動GC

直接上代碼:

  @Override public void runGc() {
      // Code taken from AOSP FinalizationTest:
      // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
      // java/lang/ref/FinalizationTester.java
      // System.gc() does not garbage collect every time. Runtime.gc() is
      // more likely to perfom a gc.
      Runtime.getRuntime().gc();
      enqueueReferences();
      System.runFinalization();
    }

白名單機制

RefWatcher 構(gòu)造器支持設計白名單。

 /** @see ExcludedRefs */
  public final T excludedRefs(ExcludedRefs excludedRefs) {
    this.excludedRefs = excludedRefs;
    return self();
  }

目前Android系統(tǒng)級別的一些內(nèi)存泄漏已經(jīng)加入了默認的白名單中AndroidExcludedRefs.java。
用戶也可以根據(jù)自己的需要自創(chuàng)建ExcludedRefs的實例添加到白名單Set中。

結(jié)果展示

Heap Dump 分析成功后會回調(diào) onHeapAnalyzed 方法。

  /**
   * Called after a heap dump is analyzed, whether or not a leak was found.
   * Check {@link AnalysisResult#leakFound} and {@link AnalysisResult#excludedLeak} to see if there
   * was a leak and if it can be ignored.
   *
   * This will be called from a background intent service thread.
   * <p>
   * It's OK to block here and wait for the heap dump to be uploaded.
   * <p>
   * The heap dump file will be deleted immediately after this callback returns.
   */
  protected abstract void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result);

默認的實現(xiàn)類 是 DisplayLeakService。它會把Leak信息默認顯示成通知格式。
當然也提供了供用戶重寫的方法,允許用戶對檢測結(jié)果做后續(xù)操作,保存\上報等。

  /**
   * You can override this method and do a blocking call to a server to upload the leak trace and
   * the heap dump. Don't forget to check {@link AnalysisResult#leakFound} and {@link
   * AnalysisResult#excludedLeak} first.
   */
  protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
  }

總結(jié)

LeakCanary核心思想是通過ReferenceQueue來檢測對象是否被回收。在主線程空閑時才會做檢測。 有必要的情況下會先手動出發(fā)GC。GC后還有引用會Dump Heap,通過HAHA做分析獲取引用路徑。

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

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

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