LeakCanary源碼解讀

「A small leak will sink a great ship.」 - Benjamin Franklinhtml

煤礦中的金絲雀

故事發生在工業革命後的英國,有經驗的煤礦工人都會在煤礦巷道中放幾隻金絲雀,當瓦斯氣體超標時,金絲雀會很快死掉,這樣煤礦工人能提早獲得預警,離開巷道。金絲雀的英文名就叫Canary,此後人們把煤礦中的金絲雀做爲危險預警的代名詞。java

canary in a coal mineandroid

LeakCanary

回到咱們今天的主題,在平時Android開發中,稍不注意就會寫出內存泄漏的代碼,有些甚至帶到了生產環境而咱們卻渾然不知。是否咱們能找一隻煤礦中的金絲雀呢,讓他監視着咱們的代碼,及時發現內存泄漏的風險。基於上面的需求LeakCanary就粉墨登場了。git

集成LeakCanary

集成LeakCarary其實很是的簡單,看看官方的例子github

public class ExampleApplication extends Application {
  @Override public void onCreate() {
    super.onCreate();
    setupLeakCanary();
  }

  protected void setupLeakCanary() {
    enabledStrictMode();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // 堆文件的分析是在一個獨立的進程中,因此不該該走應用初始化的邏輯      
      return;
    }
    LeakCanary.install(this);
  }

  private static void enabledStrictMode() {
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() //
        .detectAll() //
        .penaltyLog() //
        .penaltyDeath() //
        .build());
  }
}
1


複製代碼

LeakCanary.install

上面看到了LeakCanary集成的方法,很簡單,就調用了install方法。bash

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

複製代碼

總結一下install方法的主要做用:服務器

  1. 建立了一個AndroidRefWatcherBuilder對象,一看這個類名就知道它使用了構建者模式,接下來就是給這個Builder添加種種配置。app

  2. 第一個配置listenerServiceClass,它要求咱們傳入一個Class類型的Service,其做用是打印咱們的leak的信息,而且在通知欄裏發出消息,固然了假如咱們須要本身處理leak信息,好比將其上傳到服務器,就能夠複寫這裏的 DisplayLeakService,在其afterDefaultHandling方法中作相關的邏輯。dom

  3. excludedRefs 配置的是咱們要過濾的內存泄漏信息,好比Android本身的源碼中,或者一些手機廠商自定義的rom中存在的內存泄漏,咱們是不關心的,或者無能無力,咱們不想讓這部分的內存泄漏出如今咱們的結果列表中,就須要配置這個選項,固然LeakCanary已經默認了一個已知的列表。固然了你也能夠自定義這個列表。ide

  4. 接下來就調用buildAndInstall方法,返回一個RefWatcher對象。

AndroidRefWatcherBuilder.buildAndInstall

/**
   *  開始監測activity和activity的引用
   */
  public @NonNull RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    //構建RefWatcher,實現了不少的默認配置
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      if (enableDisplayLeakActivity) {
        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      }
      if (watchActivities) {
        //在application中registerActivityLifecycleCallbacks,當activity Destroy的時候
        //將activity的引用保存在RefWatcher的軟引用中
        ActivityRefWatcher.install(context, refWatcher);
      }
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }


複製代碼

該方法總結:

  1. 構建RefWatcher,實現了不少的默認設置
  2. 經過ActivityRefWatcher.install方法在application中registerActivityLifecycleCallbacks,當activity Destroy的時候,將activity的引用保存在RefWatcher的軟引用中。
  3. 經過FragmentRefWatcher.Helper.install方法監測fragment的引用。

ActivityRefWatcher.install

下面以activity爲例,講解整個流程,fragment的檢測和activity相似。在該方法中主要的操做就是上面所說的registerActivityLifecycleCallbacks了,而檢測是從Activity的onDestroy方法被調用開始。

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);
  }


複製代碼
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityDestroyed(Activity activity) {
          refWatcher.watch(activity);
        }
      };


複製代碼

RefWatcher.watch

當onActivityDestroyed被系統調用的時候,RefWatcher調用了它的watch方法,並將activity的引用傳入。watch方法中作了下面幾件事:

  1. 生成一個惟一的uuid保存在retainedKeys這個set中
  2. 將activity的引用保存在一個弱引用中
  3. 調用ensureGoneAsync方法,經過方法名能夠猜想他的做用是確保activity已經被垃圾回收,下面咱們會仔細分析這個過程

對弱引用不熟悉的讀者,這裏要注意一下KeyedWeakReference這個類,它其實是繼承的WeakReference,WeakReference有個兩個參數的構造方法,第一個參數是當前的引用,這個很好理解,另一個是一個隊列,它的做用是噹噹前對象被垃圾回收後,會將其註冊在這個隊列中。咱們在分析ensureGoneAsync方法的時候會用到這個知識點。

public void watch(Object watchedReference) {
    watch(watchedReference, "");
  }


複製代碼
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);
  }

複製代碼

RefWatcher.ensureGoneAsync

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

  @SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

    //將已經回收的對象從retainedKeys中清除
    removeWeaklyReachableReferences();
    //debug模式不進行檢測
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    //已經回收
    if (gone(reference)) {
      return DONE;
    }
    //強制進行垃圾回收
    gcTrigger.runGc();
    //再次將已經回收的對象從retainedKeys中移除
    removeWeaklyReachableReferences();
    //若是尚未回收那麼就認爲發生了內存泄漏,dump heap文件,並進行分析
    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);

      //保存heap dump中的信息,好比hprof文件、發生內存泄漏的引用、從watch到gc的時間間隔、gc所花的時間、heap dump所花的時間等
      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
          .referenceName(reference.name)
          .watchDurationMs(watchDurationMs)
          .gcDurationMs(gcDurationMs)
          .heapDumpDurationMs(heapDumpDurationMs)
          .build();
       //啓動一個單獨進程的service去分析heap dump的結果     
      heapdumpListener.analyze(heapDump);
    }
    return DONE;
  }

複製代碼

總結一下上面的流程,首先判斷當前引用對象有沒有被回收,若是沒有被回收則強制虛擬機進行一次gc,以後再判斷該引用對象是否被回收,若是尚未,則認爲發生了內存泄漏,dump Heap文件,而且對其進行分析。

RefWatcher.removeWeaklyReachableReferences

還記得咱們上面建立弱引用時,傳入了一個弱引用隊列,這個隊列中存放着就是已經被回收的對象的引用。經過這個隊列,保證retainedKeys中存放的key值對應的引用都是沒有被gc回收的。

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);
    }
  }


複製代碼

AndroidHeapDumper.dumpHeap

該方法的做用是dump heap信息到指定的文件中

@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
  @Override @Nullable
  public File dumpHeap() {

    //建立一個文件,永遠存放drump 的heap信息
    File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();

    if (heapDumpFile == RETRY_LATER) {
      return RETRY_LATER;
    }

    FutureResult<Toast> waitingForToast = new FutureResult<>();
    showToast(waitingForToast);

    if (!waitingForToast.wait(5, SECONDS)) {
      CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
      return RETRY_LATER;
    }

    Notification.Builder builder = new Notification.Builder(context)
        .setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
    Notification notification = LeakCanaryInternals.buildNotification(context, builder);
    NotificationManager notificationManager =
        (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    int notificationId = (int) SystemClock.uptimeMillis();
    notificationManager.notify(notificationId, notification);

    Toast toast = waitingForToast.get();
    try {
      //dump heap信息到剛纔建立的文件中
      Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
      cancelToast(toast);
      notificationManager.cancel(notificationId);
      return heapDumpFile;
    } catch (Exception e) {
      CanaryLog.d(e, "Could not dump heap");
      // Abort heap dump
      return RETRY_LATER;
    }
  }



複製代碼

ServiceHeapDumpListener.analyze

對dump到的heap信息進行分析是在一個獨立進程的Service中完成的。

@Override public void analyze(@NonNull HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }


複製代碼
public static void runAnalysis(Context context, HeapDump heapDump,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    setEnabledBlocking(context, HeapAnalyzerService.class, true);
    setEnabledBlocking(context, listenerServiceClass, true);
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    ContextCompat.startForegroundService(context, intent);
  }


複製代碼

HeapAnalyzerService.onHandleIntentInForeground

@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
    if (intent == null) {
      CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
      return;
    }
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);

    HeapAnalyzer heapAnalyzer =
        new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
    //分析heap dump,返回結果
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
        heapDump.computeRetainedHeapSize);
     //啓動一個新的Service用於處理返回的分析結果 
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }

複製代碼

可見這裏仍然不是真正的分析處理heap dump的地方,繼續往下找。

HeapAnalyzer.checkForLeak

終於終於咱們來到了最終分析heap dump的地方,這個方法的主要的做用經過分析heap dump,找到咱們發生內存泄漏的引用,而後計算出到GCRoot最短的引用鏈。對heap dump的分析leakcanary使用了著名的haha庫, 不過最新的版本的leakcanary已經本身實現了heap dump的分析

/**
   * Searches the heap dump for a {@link KeyedWeakReference} instance with the corresponding key,
   * and then computes the shortest strong reference path from that instance to the GC roots.
   */
  public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,
      @NonNull String referenceKey,
      boolean computeRetainedSize) {
    long analysisStartNanoTime = System.nanoTime();

    if (!heapDumpFile.exists()) {
      Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
      return failure(exception, since(analysisStartNanoTime));
    }

    try {
      listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      HprofParser parser = new HprofParser(buffer);
      listener.onProgressUpdate(PARSING_HEAP_DUMP);
      Snapshot snapshot = parser.parse();
      listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
      deduplicateGcRoots(snapshot);
      listener.onProgressUpdate(FINDING_LEAKING_REF);
      Instance leakingRef = findLeakingReference(referenceKey, snapshot);

      // False alarm, weak reference was cleared in between key check and heap dump.
      if (leakingRef == null) {
        String className = leakingRef.getClassObj().getClassName();
        return noLeak(className, since(analysisStartNanoTime));
      }
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }


複製代碼

Q&A

  1. LeakCanary中強制gc的方式

這段代碼是LeakCanary中採用Android系統源碼實現強制gc的方式

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 perform a gc.
      Runtime.getRuntime().gc();
      enqueueReferences();
      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(); } } }; 複製代碼

可是問題來了,爲何這種方式能夠實現強制gc,個人結論是(沒有通過驗證)虛擬機執行gc的時候,回收的對象須要經歷兩次標記才能被真正的回收。執行完第一次標記後,虛擬機會判斷對象是否複寫了finalize()方法,或者是否執行過finalize,若是符合其中一個條件則,不會執行finalize方法。若是被斷定須要執行finalize()方法,虛擬機會將該對象加入到名爲F-Queue的隊列中,而且在一個優先級底的線程中執行對象的finallize()方法。因此須要讓線程sleep一會,目的就是等待finallize的執行。以後執行第二次標記,這個時候若是對象沒有在finallize中復活,則該對象就會被回收。

總結

上面已經把LeakCanary的總體流程分析完了,可是因爲做者的水平有限,不少細節方面的東西可能沒有顧忌到,好比heap dump分析那塊的東西實際上是很重要的一個部分,若是你們有興趣,能夠着重的看一下。好了,這篇文章就寫到這裏了。

參考文獻

相關文章
相關標籤/搜索