內存監控LeakCanary1.6.1使用和原理分析

使用

LeakCanary 的集成過程很簡單,首先在 build.gradle 文件中添加依賴:

dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
}

然後實現自己的 Application 類:

public class LeakcanaryApplication extends Application {

    private static RefWatcher sRefWatcher;

    @Override public void onCreate() {
        super.onCreate();
        Log.e("LeakcanaryApplication", "onCreate");

        sRefWatcher = LeakCanary.install(this);  //爲了sRefWatcher一定要install

    }

    public static RefWatcher getsRefWatcher(){
        return sRefWatcher;
    }
}

在需要監控的Activity、Fragment、Service或要監控的特定對象等,

 LeakcanaryApplication.getsRefWatcher().watch(this);

這樣就集成完成了。當 LeakCanary 檢測到內存泄露時,會自動彈出 Notification 通知開發者發生內存泄漏的 Activity 和引用鏈,以便進行修復。

源碼分析

  1. 首先從入口函數LeakCanary.install(Application application)開始
public static RefWatcher install(Application application) {
  return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
      .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
      .buildAndInstall();
}
  • LeakCanary的refWatch方法
public static AndroidRefWatcherBuilder refWatcher(Context context) {
    return new AndroidRefWatcherBuilder(context);
  }

refWatcher() 方法新建了一個 AndroidRefWatcherBuilder 對象,該對象繼承於 RefWatcherBuilder 類,配置了一些默認參數,利用建造者構建一個 RefWatcher 對象

  • AndroidRefWatcherBuilder.listenerServiceClass()方法
public AndroidRefWatcherBuilder listenerServiceClass(
    Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
  return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}

public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
  this.heapDumpListener = heapDumpListener;
  return self();
}

==listenerServiceClass() 方法綁定了一個後臺服務 DisplayLeakService,這個服務主要用來分析內存泄漏結果併發送通知==。可以自定義處理,繼承DisplayLeakService實現自定義的監控處理Service。

  • excludedRefs()

excludedRefs() 方法定義了一些對於開發者可以忽略的路徑,意思就是即使這裏發生了內存泄漏,LeakCanary 也不會彈出通知。這大多是系統 Bug 導致的,無需用戶進行處理。

  • AndroidRefWatcherBuilder.buildAndInstall()

最後調用 buildAndInstall() 方法構建 RefWatcher 實例並開始監聽 Activity 的引用

要點分析

  • install是有返回值的—RefWatcher,這個對象非常重要,LeakCanary就是通過這個對象進行Activity的內存監控

  • RefWatcher是通過Builder模式創建的;

  • install內部一連串的鏈式調用,最後調用了buildAndInstall()方法;

 public RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

watchActivities/watchFragments默認值都是true,所以ActivityRefWatcher和FragmentRefWatcher都將被啓用;

ActivityRefWatcher用於監控Activity的內存泄漏;

FragmentRefWatcher用於監控Fragment的內存泄漏;

  1. 用ActivityRefWatcher來進一步分析==LeakCanary是如何監控內存泄漏的==.

ActivityRefWatcher.install(context, refWatcher)方法

public static void install(Context context, RefWatcher refWatcher) {
    Application application = (Application) context.getApplicationContext();
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }

要點分析:

1、registerActivityLifecycleCallbacks是Android Application的一個方法,註冊了該方法,應用中每一個Activity的生命週期變化都會通過該方法回調回來

2、registerActivityLifecycleCallbacks方法傳入的參數是activityRefWatcher.lifecycleCallbacks,我們到ActivityRefWatcher中看下該方法的實現

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

ActivityRefWatcher只監聽了onActivityDestroyed方法,也就是說每一個Activity調用onDestory的時候都會回調到onActivityDestroyed這個方法,通知LeakCanary該Activity應該被銷燬

到這來我們知道了爲什麼只執行LeakCanary.install(this)一條語句就可以完成對整個app的Activity內存泄漏進行監聽了.

接下來看看ActivityRefWatcher具體是如何監聽內存泄漏的,RefWatcher有兩個核心方法: watch() 和 ensureGone()

先看watch方法

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);
  }
  • ==監聽提供的引用,檢查該引用是否可以被回收==。這個方法是非阻塞的,因爲檢測功能是在Executor中的異步線程執行的。

  • checkNotNull:分別檢測watchedReference、referenceName是否爲空,如果爲空則拋出異常結束;

  • ==隨機生成一個key,該key用於唯一標識已產生內存泄漏的對象,或者準備檢測的對象==;

  • 創建KeyedWeakReference對象,並調用另一個核心方法ensureGone;

接着看另一個ensureGone方法:

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

    removeWeaklyReachableReferences();

    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    if (gone(reference)) {
      return DONE;
    }
    gcTrigger.runGc();
    removeWeaklyReachableReferences();
    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();

      heapdumpListener.analyze(heapDump);
    }
    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.
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key);
    }
  }
  
  • ensureGoneAsync() 裏面會 調用watchExecutor.execute(),watchExecutor 的真正實現是 AndroidWatchExecutor,IdleHandler會==在主線程的空閒期得到執行==,當執行的時候會調用postToBackgroundWithDelay()。這個方法爲 LeakCanary 檢測的重試機制(創建dump文件或者toast顯示超5秒等情況重試執行ensureGone)。

  • ==這是一個異步任務,不會對主線程造成阻塞==。

  • removeWeaklyReachableReferences():==移除已經回收的弱引用對象的key。==

  • 如果需要判斷的對象已經銷燬,則不繼續執行。

  • 如果==移除之後還是存在該引用的key則手動再GC一次==:gcTrigger.runGc()。

  • 然後==二次調用removeWeaklyReachableReferences()再判斷是否已經銷燬==。

  • ==如果二次確認還是存在則判斷爲內存泄漏了==,開始.hprof文件的dump。

  • 調用heapdumpListener.analyze(heapDump)進行泄漏分析。

階段總結一下

  • ==弱引用與ReferenceQueue聯合使用,如果弱引用關聯的對象被回收,則會把這個弱引用加入到ReferenceQueue中==;通過這個原理,可以看出removeWeaklyReachableReferences()執行後,會對應刪除已被回收的對象的KeyedWeakReference的數據。==如果這個引用繼續存在,那麼就說明沒有被回收==。

  • ==爲了確保最大保險的判定是否被回收,一共執行了兩次回收判定,包括一次手動GC後的回收判定。兩次都沒有被回收,很大程度上說明了這個對象的內存被泄漏了,但並不能100%保證;因此LeakCanary是存在極小程度的誤差的。==

  1. 內存泄漏最短路徑分析

hprof文件已經生成好了,接下來就是內存泄漏路徑生成分析了,上一步heapdumpListener.analyze(heapDump)的調用。

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

要點分析:

  • ==調用了HeapAnalyzerService,在單獨的進程中進行分析;==
public final class HeapAnalyzerService extends ForegroundService
    implements AnalyzerProgressListener {

  private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
  private static final String HEAPDUMP_EXTRA = "heapdump_extra";

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

  public HeapAnalyzerService() {
    super(HeapAnalyzerService.class.getSimpleName(), R.string.leak_canary_notification_analysing);
  }

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

    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
        heapDump.computeRetainedHeapSize);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }

  @Override public void onProgressUpdate(Step step) {
    int percent = (int) ((100f * step.ordinal()) / Step.values().length);
    CanaryLog.d("Analysis in progress, working on: %s", step.name());
    String lowercase = step.name().replace("_", " ").toLowerCase();
    String message = lowercase.substring(0, 1).toUpperCase() + lowercase.substring(1);
    showForegroundNotification(100, percent, false, message);
  }
}

要點分析:

  • runAnalysis方法中最後一句:ContextCompat.startForegroundService(context, intent)啓動HeapAnalyzerService
  • HeapAnalyzerService繼承自ForegroundService,服務啓動時會調用onHandleIntentInForeground方法;

HeapAnalyzer中核心的方法是checkForLeak,我們來看下它的具體實現:

/**
   * 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 AnalysisResult checkForLeak(File heapDumpFile, 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) {
        return noLeak(since(analysisStartNanoTime));
      }
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }

要點分析:

  • 先來翻譯一下官方對該方法的註釋:
在dump的堆信息中通過key查找出內存泄漏的弱引用對象,並且計算出最短的GC路徑;
  • Snapshot snapshot = parser.parse()–生成文件的快照

  • deduplicateGcRoots(snapshot)–過濾重複的內存泄漏對象

  • findLeakingReference(referenceKey, snapshot)–==在快照中根據referenceKey查找是否有對應的內存泄漏對象==,如果獲取到的leakingRef爲空,則說明內存已經被回收了,不存在內存泄漏的情況;如果leakingRef不爲空則進入下一步查找內存泄漏對象的GC最短路徑;

  • findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize)–查找內存泄漏對象的GC最短路徑

LeakCanary的核心源碼已經都已經分析完了,剩下的就是如果在頁面上顯示出來了。

image.png

監控原理總結

  1. ==LeakCanary主要是通過Application的registerActivityLifecycleCallbacks方法監控每一個Activty的Destory之後對象是否被收回.==

  2. ==在Activity Destory之後,RefWatch的watch方法將被調用,watch方法會通過一個隨機生成的key將這個弱引用關聯到一個ReferenceQueue,然後調用ensureGone();==
    (watch也可以傳入其他需要監控的對象,將檢測的對象放入弱引用中並且關聯到引用隊列中,查看引用隊列中是否存在引用,如果發現泄露,dump出信息進行分析)

(當一個軟引用/弱引用對象被垃圾回收後,Java虛擬機就會把這個引用加入到與之關聯的引用隊列中;)

  1. ==RefWatch的ensureGone()方法中會先確認一次是否已經被回收,如果發現沒有被回收,則主動GC一下,然後在次確認是否被回收,如果還是沒有回收則判斷爲內存泄漏==;

(==判斷有沒被回收的依據==:弱引用與ReferenceQueue聯合使用,如果弱引用關聯的對象被回收,則會把這個弱引用加入到ReferenceQueue中,removeWeaklyReachableReferences()會清除掉retainedKeys中相應的key。在調用gone()方法時判斷retainedKeys是否還包含相應的key,沒包含說明回收了,包含說明沒有回收)

  1. 一旦確認是內存泄漏,則開始dump信息到hprof文件中,並調用heapdumpListener.analyze(heapDump)開始內存分析;

  2. ==內存分析是在HeapAnalyzerService服務中進行的,屬於一個單獨的進程==;

  3. HeapAnalyzerService的runAnalysis中創建HeapAnalyzer對象並調用它的一個核心方法checkForLeak();

  4. ==HeapAnalyzer的checkForLeak()會先解析hprof文件並生成快照文件,然後對快照中的泄漏對象進行去重,去重後根據第2步中的key去獲取泄漏對象,如果對象爲空則說明對象已經被回收,如果不爲空則通過findLeakTrace()方法計算出最短GC路徑==,並顯示到DisplayLeakActivity頁面,提醒開發者存在內存泄漏.

默認情況只對Activity和Fragment進行監控,也可以對其他模塊的對象進行監控。

LeakcanaryApplication.getsRefWatcher().watch(this);

https://blog.csdn.net/hust_twj/article/details/90645228

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