内存监控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 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 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的核心源码已经都已经分析完了,剩下的就是如果在页面上显示出来了。

内存监控LeakCanary1.6.1使用和原理分析_第1张图片
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

你可能感兴趣的:(第三方库,性能优化,android,LeakCanary)