LeakCanary源碼分析

概述

LeakCanary是用來檢測 Java 和 Android 內存泄露的工具。

LeakCanary的原理非常簡單。正常情況下一個Activity在onDestroy之後就要銷燬,LeakCanary做的就是在一個Activity onDestroy之後將它放在一個WeakReference中,然後將這個WeakReference關聯到一個ReferenceQueue。這個ReferenceQueue的作用是,當Activity被回收的時候,系統會將其Activity對應的WeakReference對象加入到ReferenceQueue

然後我們查看ReferenceQueue是否存在WeakReference對象,如果存在說明Activity已經被回收。如果不存在,執行GC操作,再查看是否被回收。如果不存在則證明該Activity泄漏了,之後Dump出heap信息,並用haha這個開源庫去分析泄漏路徑。

LeakCanary的使用很簡單,如下:

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...
  }
}

源碼分析

install

走進install():

  public static RefWatcher install(Application application) {
    // 添加了監聽器,排除了一些不需要觀察的類並且完成了創建
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }

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

  public static void install(Application application, RefWatcher refWatcher) {
    new ActivityRefWatcher(application, refWatcher).watchActivities();
  }

以上代碼所做的主要內容就是創建了一個Activity內存泄露的監聽器,注入到了Application中。

watchActivities

然後進入watchActivities()

  public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    // 註冊了一個Activity生命週期的監聽器
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }

  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) {
          // 當Activity銷燬的時候回調refWatcher的watch()函數
          ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
      };

  void onActivityDestroyed(Activity activity) {
    refWatcher.watch(activity);
  }

watch

watch()最後調用的重載函數:

  public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    // 判空
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    // 記住開始觀查的時間
    final long watchStartNanoTime = System.nanoTime();
    // 隨機生成一個key
    String key = UUID.randomUUID().toString();
    // 加入到一個集合中
    retainedKeys.add(key);
    // 將Activity包裹成一個弱引用對象
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    // 檢測內存泄露,確保Activity真的被回收
    ensureGoneAsync(watchStartNanoTime, reference);
  }

  private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        // 這個方法會在Android主線程空閒的時候執行
        return ensureGone(reference, watchStartNanoTime);
      }
    });

ensureGone

進入ensureGone():

  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    // 計算從開始觀察到gc所用的時間
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    // 清除已經進入ReferenceQueue的弱引用
    // 把已被回收的對象的key從retainedKeys移除,剩下的key都是未被回收的對象
    removeWeaklyReachableReferences();

    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    if (gone(reference)) {
      // 如果當前的對象已經弱可達,說明不會造成內存泄漏
      return DONE;
    }
    // 否則手動調用gc,以防止系統並沒有回收,誤判
    gcTrigger.runGc();
    // 清除已經進入ReferenceQueue的弱引用
    removeWeaklyReachableReferences();
    if (!gone(reference)) {
      // 內存泄露
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
      // dump出來heap
      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;
  }

  private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
  }

  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.
    // 在實際垃圾回收之前弱引用就會被加入ReferenceQueue隊列
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key);
    }
  }

其中gcTrigger.runGc();如何保證肯定gc呢:

public interface GcTrigger {
  GcTrigger DEFAULT = new GcTrigger() {
    @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.
      // 觸發系統gc操作
      Runtime.getRuntime().gc();
      // 通過強制限制100毫秒的時間給gc
      enqueueReferences();
      // 強制調用已經失去引用的對象的finalize方法
      System.runFinalization();
    }

    private void enqueueReferences() {
      // Hack. We don't have a programmatic way to wait for the reference queue daemon to move
      // references to the appropriate queues.
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        throw new AssertionError();
      }
    }
  };

  void runGc();
}

大概意思是system.gc()並不會每次都立即執行,這裏從AOSP中拷貝一段GC的代碼,從而保證能夠進行垃圾清理工作。

後續的如何導出文件,分析,提示內存泄露並不是重點,這裏省略了。

疑問

判斷引用是否被回收的時候,爲什麼不直接使用reference.get(),是不是因爲這個時候reference.get()的結果有可能不是null,但是已經加入了ReferenceQueue,證明馬上就要被回收了。使用reference.get(),更加準確。

參考:
1.深入理解 Android 之 LeakCanary 源碼解析
2.LeakCanary源碼分析
3.譯文:理解Java中的弱引用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章