LeakCanary详解与源码分析

1,558 阅读17分钟

一、前言

1.简介

A small leak will sink a great ship —— Benjamin Franklin

千里之堤,毁于蚁穴。这篇文章要分析的就是squareLeakCanary。LeakCanry主要是用于检测 Activity 以及 Fragment 中是否存在内存泄漏,并且自动弹出通知告知用户发生了内存泄漏,且最终以 UI 的形式向我们展示内存泄漏对象的引用链,以便我们能精确的定位到内存泄漏的代码。

image.png

2.使用

2.1添加依赖

dependencies {
  // debug 版本依赖
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
  // release 版本依赖  
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
  // 如果使用了 support fragment,请同时依赖
  debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'
}

2.2 初始化并开始检测内存泄漏

一般情况下,在你的 Application#onCreate() 方法里面进行初始化

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // 不检测 LeakCanary 的分析进程
      return;
    }
    // 初始化并监听内存泄漏
    LeakCanary.install(this);
    .....
  }
}

二、框架概述

1.框架

优秀的框架之所以优秀,并不只是它向我们提供了强大的功能和解决了我们的问题,相对更重要的还在于它对模块的清晰划分以及模块之间层叠有秩的层次。通过对各模块的依赖关系的梳理以及对源码的理解,梳理出如下的框架图。

leakcanary框架图

**leakcanary-sample:**就是我们的应用程序。

leakcanary-android & leakcanry-android-no-op: LeakCanary 一般建议工作在 Debug 模式下,这时我们依赖 leakcanary-android 就可以了。当有检测到内存泄漏时其会先冻结进程并 dump 出内存快照并进行分析。而在 release 模式下就依赖 leakcanry-android-no-op,其不会产生后续的分析行为,看架图的依赖关系就知道了。

leakcanary-analyzer: 主要是通过 haha 进行内存泄漏的分析。

leakcanary-watcher: 对对象的引用进行监测,当有内存泄漏时先进行 heap dump,然后再告知 leakcanary-analyzer 进行接下来的分析工作。

haha: 这是个 square 的另一个开源库(haha's github),专门用于自动分析 Android heap dump。类似的库还有vshor/matAndroMAT 以及鼎鼎大名的 Eclipse Memory Analyzer

2.主体类图

根据框架图的层次关系,以及对源码的理解,梳理出如下主体类图。

LeakCanary主体类图

(1) LeakCanary 以及左边的方框中的类都是属于 leakcanary-android 层的,这里边除了 LeakCanary 这个对外提供的操作接口,其他基本上是 RefWatcher 中的各个子组件的具体实现类。

(2) RefWatcher 及其框内的东西都是属于 leakcanary-watcher 层的。这里的 RefWatcher 是通过 AndroidRefWatcherBuilder 来构造的。通过继承 RefWatcherBuilder 以及实现各个子组件的接口,我们也可以实现自己的 watcher 层。各个子组件的作用在后面的代码分析过程还会再详细说明。

(3) HeapAnalyzer 属于 leakcanary-anlyzer层,主要就是分析 heap hprof 文件并最终通知给应用层。

3.工作原理

看了框架图以及主体类图后对 LeakCanary 这个框架应该有一个比较全局的认知了,在这个基础上,我们再简要过一下它的基本工作原理。先来看看它的原理图。

工作原理

(1) App,也就是我们的应用通过 LeakCanary 这个对外提供的接口来初始化好框架,其主要是初始化好 RefWatcher。

(2) 通过在一定的时机,框架内实现的是监听 Activity 与 Fragment 的生命周期,向 RefWatcher 添加监测的引用。RefWatcher 在主线程 idle 后或者进行一次强制 gc 后再判断该引用是否已经被回收来判定其是否有内存泄漏。

(3) 当 RefWatcher 检测到有内存泄漏后,便会通过其组件 HeapDumper dump 出内存堆的 hprof 文件,并交由 HeapAnalyzer 进行分析。

(4) HeapAnalyzer 分析出结果后便会通知到 App。

LeakCanary 内部只帮我们监测了 Activity 以及 Fragment 存在的内存泄漏问题,理论上来说,我们在对 App 的业务理解基础也能找出可以监测的时机,从而监测更多的内存泄漏的问题。

三、源码分析

1.demo

LeakCanary 不同于其他的库的使用,其非常简单,这在第一部分也已经介绍了,其主要就是一句代码的事儿。

LeakCanary.install(this);

代码分析就从这里开始了。

2.初始化 install()

先来看一看 LeakCanary 的时序图,分析代码时,也是沿着时序图一步一步来分析。

LeakCanary初始化时序图

2.1 LeakCanary#install()

/**
   * 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 
        // 创建 AndroidRefWatcherBuilder
        refWatcher(application)
        // 设置用于监听内存泄漏的分析结果的 Service
        .listenerServiceClass(DisplayLeakService.class)
        // 忽略检测的引用
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        // 构建并且初始化 RefWatcher
        .buildAndInstall();
  }

方法中先通过 refWatcher() 创建了 AndroidRefWatcherBuilder,这是一个 builder 设计模式,接下来是一系列的链式调设置参数,最后再通过 buildxxx() 来构建出最后具体的对象。refWatcher() 的代码很简单,就是 new 一个 AndroidRefWatcherBuilder 对象,如下。 LeakCanary#refWatcher()

  public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
    return new AndroidRefWatcherBuilder(context);
  }

2.2 AndroidRefWatcherBuilder#listenerServiceClass()。

  /**
   * Sets a custom {@link AbstractAnalysisResultService} to listen to analysis results. This
   * overrides any call to {@link #heapDumpListener(HeapDump.Listener)}.
   */
  public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
      @NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
  }

方法的主要作用是设置一个用于监听内存泄漏的分析结果的 Service,这里传入的是 DisplayLeakService。

/**
 * Logs leak analysis results, and then shows a notification which will start {@link
 * DisplayLeakActivity}.
 * <p>
 * You can extend this class and override {@link #afterDefaultHandling(HeapDump, AnalysisResult,
 * String)} to add custom behavior, e.g. uploading the heap dump.
 */
public class DisplayLeakService extends AbstractAnalysisResultService {......}

从它的定义中的注释可知,这是当有分析结果后,其便会发出一个 notification,并且通过这个 notification 来启动 DisplayLeakActivity,就是我们上面截图看到的那个 Activity。关于 DisplayLeakService ,它的类图以及继承关系如下。

Service.jpg

可以看出这是一个 IntentServcie,即完成工作后会主动退出 Service 而不会长期占用内存。在这个类图中,DisplayLeakService 主要需要实现钩子函数 onHeapAnalyzed(),在这里完成其主要的工作。

然后再来看 ServiceHeapDumpListener 的定义。

public final class ServiceHeapDumpListener implements HeapDump.Listener {
  .....
  @Override public void analyze(@NonNull HeapDump heapDump) {
    ......
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }
}

这是 HeapDump.Listener 的具体实现类,主要工作在 hook 方法 analyze() 里面,就是当发生内存泄漏,并且 dump 完了 heap hprof 后便会回调到该方法,然后通过 HeapAnalyzerService.runAnalysis() 调用来启动 HeapAnalyzerService 来进行 heap hprof 的分析。具体的 heap hprof 分析后面还会细讲。

最后是 heapDumpListener() 的调用,该方法的调用就是将刚才 new 出来的 ServiceHeapDumpListener 保存在属性 heapDumpListener 里。

**小结:**这里小结一下,通过 listenerServiceClass() 调用确定了两个点: (1) 由 ServiceHeapDumpListener 监听内存泄漏的通知,并启动 HeapAnalyzerService 来进行具体的分析工作。

(2) 由 DisplayLeakService 监听 heap hprof 分析完成后的工作,然后通过在状态栏发出 notification 来通知用户。

2.3 RefWatcherBuilder#excludedRefs()

  public final T excludedRefs(ExcludedRefs excludedRefs) {
    heapDumpBuilder.excludedRefs(excludedRefs);
    return self();
  }

主要是设置那些用于忽略的已知的内存泄漏,这个其实并不是主路径,那就先暂时不深入展开了。 ###2.4 AndroidRefWatcherBuilder#buildAndInstall()

/**
   * Creates a {@link RefWatcher} instance and makes it available through {@link
   * LeakCanary#installedRefWatcher()}.
   *
   * Also starts watching activity references if {@link #watchActivities(boolean)} was set to true.
   *
   * @throws UnsupportedOperationException if called more than once per Android process.
   */
  public @NonNull RefWatcher buildAndInstall() {
   ......
    // 创建最终返回的 RefWatcher
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      // 设置 DisplayLeakActivity 为 enable 状态
      LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      // 如果需要监测 activity 则启动 activity 的监测
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }
      // 如果需要监测 activity 则启动 activity 的监测
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

方法主要就是最后创建出 RefWatcher 返回给调用者,而过程就如方法名先 build 再 install,先来分析 build。

2.4.1 RefWatcherBuilder#build()

public final RefWatcher build() {
    // 如果禁用了返回 RefWatcher.DISABLED
    if (isDisabled()) {
      return RefWatcher.DISABLED;
    }
    //  初始化待忽略的已知内存泄漏集
    if (heapDumpBuilder.excludedRefs == null) {
      heapDumpBuilder.excludedRefs(defaultExcludedRefs());
    }
    // 初始化 heap dump 监听器
    HeapDump.Listener heapDumpListener = this.heapDumpListener;
    if (heapDumpListener == null) {
      heapDumpListener = defaultHeapDumpListener();
    }
    // 初始化 DebuggerController
    DebuggerControl debuggerControl = this.debuggerControl;
    if (debuggerControl == null) {
      debuggerControl = defaultDebuggerControl();
    }
    // 初始化 HeapDumper
    HeapDumper heapDumper = this.heapDumper;
    if (heapDumper == null) {
      heapDumper = defaultHeapDumper();
    }
    // 初始化 WatchExecutor
    WatchExecutor watchExecutor = this.watchExecutor;
    if (watchExecutor == null) {
      watchExecutor = defaultWatchExecutor();
    }
    // 初始化 GcTrigger
    GcTrigger gcTrigger = this.gcTrigger;
    if (gcTrigger == null) {
      gcTrigger = defaultGcTrigger();
    }

    if (heapDumpBuilder.reachabilityInspectorClasses == null) {
      heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
    }

    return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
        heapDumpBuilder);
  }

build() 的工作主要就是初始化其内部的各个组件。让我们先对这些组件有一个粗略的认知。

(1) HeapDump.Listener,这里是 ServiceHeapDumpListener。

(2) DebuggerControl,是否 app 被 attach 到 debug 模式下,这种情况不监测。这里默认就是 AndroidDebuggerControl。

(3) HeapDumper,见名知义,dump 出内存堆 heap hprof 并保存到文件中。这里默认是 AndroidHeapDumper。

(4) WatchExecutor,监测调用度器,就是在一定延迟后执行一次监测操作。这里是AndroidWatchExecutor,其主要是在等在主线程 Idle (空闲) 后,向后台线程发起查看内存是否泄漏。

(5) GcTrigger,这个也是见名知义,就是 gc 触发器。

对 RefWatcherBuilder 内部的各个组件有一个认知后,再来分析 install()。

2.4.2 ActivityRefWatcher#install()

  public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
    Application application = (Application) context.getApplicationContext();
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
// 注册 Activity 的生命周的回调。
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }

主要的手段是监听 Activity 声明周期的回调,来看一看 lifecycleCallbacks 的实现。

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

其只实现了 onActivityDestroyed()。也就是当 Activity 发生 onDestroy() 后开始监测这个 Activity 是否有内存泄漏。进一步来看 refWatcher#watch() 的实现。

public void watch(Object watchedReference, String referenceName) {
    //如果是禁用状态就返回了
    if (this == DISABLED) {
      return;
    }
    ......
    final long watchStartNanoTime = System.nanoTime();
    // 给引用指定的 key,并将其加入到 retainedKeys 中,将来作为弱引用对象是否已经被回收的依据。
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    // 创建一个弱引用,并指定对象为 activity,同时还指定了 queue。只要WeakReferences指向的对象变得弱可及,它就被加入队列。这是在最终确定或垃圾收集实际发生之前。
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    // 触发一个异步的调度。
    ensureGoneAsync(watchStartNanoTime, reference);
  }

主要就是创建一个弱引用并发出一个异步调度。而这个异步调度是由 AndroidWatchExecutor 来控制的,也就说其会等待主线程 idle 后执行这个调度。那这个调度作了什么呢?

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

调度里就一句代码,即调用了 ensureGone()。

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    // 先把ReferenceQueue中的弱引用从 retainedKeys 中移除。
    removeWeaklyReachableReferences();
    
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    // 如果当前引用不在 retainedKeys 中,说明已经移除了,也说明弱引用的对象可回收
    if (gone(reference)) {
      return DONE;
    }
    // 如果没有,则强制触发一次 gc
    gcTrigger.runGc();
    // 再尝试移除 retainedKeys
    removeWeaklyReachableReferences();
   // 如果还在说明有内存泄漏了。
    if (!gone(reference)) {
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
      // 发起 heap dump
      File heapDumpFile = heapDumper.dumpHeap();
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
     // 获取 heapDump
      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
          .referenceName(reference.name)
          .watchDurationMs(watchDurationMs)
          .gcDurationMs(gcDurationMs)
          .heapDumpDurationMs(heapDumpDurationMs)
          .build();
      // 通知 heapdumpListener 进行分析
      heapdumpListener.analyze(heapDump);
    }
    return DONE;
  }

**小结:**主要的处理逻辑都在代码的注释里。这里再总结一下监测内存泄漏的原理:

(1) 监测Activity 的生命周期的 onDestroy() 的调用。

(2) 当某个 Activity 的 onDestroy() 调用后,便对这个 activity 创建一个带 ReferenceQueue 的弱引用,并且给这个弱引用创建了一个 key 保存在 retainedKeys 中。

(3) 在 Android 的 Framework 中,当一个 Activity 被 destroy 后一般会产生一次 gc,并且还会产生一个 idle。

(4) 如果这个 activity 可以被回收,那么弱引用就会被添加到 ReferenceQueue 中。

(5) 等待主线程进入 idle后,通过一次遍历,在 ReferenceQueue 中的弱引用所对应的 key 将从 retainedKeys 中移除,说明其没有内存泄漏。

(6) 如果 activity 没有被回收,先强制进行一次 gc,再来检查,如果 key 还存在 retainedKeys 中,说明 activity 不可回收,同时也说明了出现了内存泄漏。

2.4.3 FragmentRefWatcher#Helper#install()

public static void install(Context context, RefWatcher refWatcher) {
      List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();

      if (SDK_INT >= O) {
        // android sdk O 版本及其以上中的 fragment
        fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
      }

      try {
        // support 包中的 fragment
        Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
        Constructor<?> constructor =
            fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
        FragmentRefWatcher supportFragmentRefWatcher =
            (FragmentRefWatcher) constructor.newInstance(refWatcher);
        fragmentRefWatchers.add(supportFragmentRefWatcher);
      } catch (Exception ignored) {
      }

      if (fragmentRefWatchers.size() == 0) {
        return;
      }

      Helper helper = new Helper(fragmentRefWatchers);

      Application application = (Application) context.getApplicationContext();
// 监测 Activity 的生命周期。
application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
    }

这段代码首先创建了一个 sdk 版本的 AndroidOFragmentRefWatcher,然后再创建一个 support 版本的 SupportFragmentRefWatcher,且它们都保存在了 fragmentRefWatchers 列表中。最后添加了 Activity 生命周期的监听,而监听 callback 是 Helper 的 callback。

private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
        new ActivityLifecycleCallbacksAdapter() {
          @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
            for (FragmentRefWatcher watcher : fragmentRefWatchers) {
              watcher.watchFragments(activity);
            }
          }
        };

这里只实现了 onActivityCreated(),而代码的逻辑就是当 Activity 创建时便遍历所有的 FragmentRefWatcher 开始监测 fragment。以 AndroidOFragmentRefWatcher 为例来看一看。

@Override public void watchFragments(Activity activity) {
    FragmentManager fragmentManager = activity.getFragmentManager();
    fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
  }

这里有点巧妙了,即通过 FragmentManager 来注册 fragment 的生命周期的回调,再来看看这个 callback。

private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
      new FragmentManager.FragmentLifecycleCallbacks() {

        @Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
          View view = fragment.getView();
          if (view != null) {
            refWatcher.watch(view);
          }
        }

        @Override
        public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
          refWatcher.watch(fragment);
        }
      };

其监听了 onFragmentViewDestroyed() 和 onFragmentDestroyed(),这两个分别针对了 view 和 fragment 的监测是否存在泄漏。而后续的 watch 过程就和 Activity 是一样的了。

与 Activity 的不同点在于对生周期的监听实现,Fragment 的生命周期的监听是通过 FragmentManager 来注册实现的。其他 watch 的过程都是一样的。

小结: 到这里,所有的初始化工作已经完成了,等待接下来的内存泄漏的发生了。总的来说,这是一个抽丝剥茧的过程,只不过这个过程在这里的分析过程中是一个逆向的:

(1) 我们先知道了当 heap hprof dump 完成后是由 DisplayLeakService 来处理的。

(2) 然后知道了当 heap hprof 分析完成后是由 ServiceHeapDumpListener 监听到的,而交由 HeapAnalyzerService 进行分析的。

(3) 最后我们才知道通过监听 Activity 与 Fragment 的生命周期,以及利用弱引用在 gc 后会被立即回收的特性来判断是否有内存泄漏的发生。

3. dump heap hprof 文件

前面的分析中已经知道,当 RefWatcher 监测到有内存泄漏后便会进行 heap dump 的操作以获取当前内存的 heap hprof。这个代码的实现其实在前面分析的过程中已经见过了,即在 RefWatcher#ensureGone() 方法里面,这里再来仔细分析一下。

// 1. dump 出 heap hprof 文件
File heapDumpFile = heapDumper.dumpHeap();
      ......
      // 2. 包装成一个 HeapDump 对象
      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
          .referenceName(reference.name)
          .watchDurationMs(watchDurationMs)
          .gcDurationMs(gcDurationMs)
          .heapDumpDurationMs(heapDumpDurationMs)
          .build();
      // 3.交给 listener 做进一步处理
      heapdumpListener.analyze(heapDump);

主要的过程就 3 个步骤,而根据前面的分析,这里的 HeapDumper 就是 AndroidHeapDumper。那这里的 dumpHeap 就是调用的它的实现了。

public File dumpHeap() {
    File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
    ......
    try {
     // dump hprof
      Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
      ......
      return heapDumpFile;
    } catch (Exception e) {
      ......
    }
  }

看到了这里的关键实现就是通过 Debug.dumpHprofData() 来 dump 出当前的内存 hprof 文件。

最后,这个 hprof 文件以及 KeyedWeakReference.key ,KeyedWeakReference.name 都会被封装在 HeapDump 中,然后再将这个 HeapDump 经由 ServiceHeapDumpListener 和 HeapAnalyzerService 交给 leakcanary-analyzer 模块进行分析。那就来看看其是如何分析的吧。

4.分析 heap hprof

4.1 checkForLeak

heap dump 完之后如何监听,如何交给 leakcanary-analyzer 的过程很简单,就是启动 HeapAnalyzerService,然后调起 HeapAnalyzer#checkForLeak() 方法进行 hprof 的分析。

/**
   * 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) {
    ......

    try {
      listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
      // 1.构建内存映射的 HprofBuffer,针对大文件的一种快速的读取方式,其原理是将文件流的通道与  ByteBuffer 建立起关联,并只在真正发生读取时才从磁盘读取内容出来。
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);  
     // 2.构造 Hprof 解析器
      HprofParser parser = new HprofParser(buffer);
      listener.onProgressUpdate(PARSING_HEAP_DUMP);
     // 3.获取快照
      Snapshot snapshot = parser.parse();
      listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
      // 4.去重 gcRoots
      deduplicateGcRoots(snapshot);
      listener.onProgressUpdate(FINDING_LEAKING_REF);
      // 5.搜索内存泄漏的索引
      Instance leakingRef = findLeakingReference(referenceKey, snapshot);
      ......
      // 6.搜索索引链,并作为结果返回
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }

前面 3 个步骤中,通过 HprofBuffer 对 heap hprof 文件进行内存映射,然后由 HprofParser 进行解析获取 heap hrpof 的 snapshot,这一过程都是在调用 haha 库进行的。其主要原理就是通过 hrpof 文件协议进行解析,这里就不深入展开其原理实现了,后面有机会再来分析 haha 库的实现。

后 3 个步骤就是这里要分析的重点。先来看看去重 gcRoots。

4.2 deduplicateGcRoots

/**
   * Pruning duplicates reduces memory pressure from hprof bloat added in Marshmallow.
   */
  void deduplicateGcRoots(Snapshot snapshot) {
    // THashMap has a smaller memory footprint than HashMap.
    final THashMap<String, RootObj> uniqueRootMap = new THashMap<>();
    final Collection<RootObj> gcRoots = snapshot.getGCRoots();
    for (RootObj root : gcRoots) {
      String key = generateRootKey(root);
      // 通过 root 来 generateRootKey,如果相同则去重
      if (!uniqueRootMap.containsKey(key)) {
        uniqueRootMap.put(key, root);
      }
    }
    // Repopulate snapshot with unique GC roots.
    gcRoots.clear();
    uniqueRootMap.forEach(new TObjectProcedure<String>() {
      @Override public boolean execute(String key) {
        // 把不相同的全部添加到 gcRoots 中
        return gcRoots.add(uniqueRootMap.get(key));
      }
    });
  }

这里主要关心的是 generateRootKey() 的实现。

  private String generateRootKey(RootObj root) {
    return String.format("%s@0x%08x", root.getRootType().getName(), root.getId());
  }

对象的类型以及ID,是由虚拟机 ART 分配的,一般来说在一个进程里肯定是唯一的。再来继续看 findLeakTrace()。

4.3 findLeakingReference

private Instance findLeakingReference(String key, Snapshot snapshot) {
    // 搜索类 KeyedWeakReference 
    ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
    ......
    List<String> keysFound = new ArrayList<>();
    // 遍历KeyedWeakReference类的所有实例对象
    for (Instance instance : refClass.getInstancesList()) {
     // 获取对象的所有属性
      List<ClassInstance.FieldValue> values = classInstanceValues(instance);
      // 看其是否包含有属性 key
      Object keyFieldValue = fieldValue(values, "key");
     // 对于没有统一设置为 null
      if (keyFieldValue == null) {
        keysFound.add(null);
        continue;
      }
      // 找到了就作为候选实例的属性
      String keyCandidate = asString(keyFieldValue);
     // 如果该属性值与 key 相等,就认为是找到了
      if (keyCandidate.equals(key)) {
        return fieldValue(values, "referent");
      }
      keysFound.add(keyCandidate);
    }
    ......
  }

这段代码的意思就是通过搜索 KeyedWeakReference 这个类的所有实例对象,然后看其对象中的 key 的值与所要搜索的对象的 key 的值是否相等,相等则为要所找到的对象的引用。这个 key 是什么呢?这个 key 就是前面在 RefWatcher#watch() 中通过 UUID 所构造出来的。找到索引后,接下来就是搜索其索引链了。

4.4 findLeakTrace

private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
      Instance leakingRef, boolean computeRetainedSize) {
    listener.onProgressUpdate(FINDING_SHORTEST_PATH);
    // 寻找最短路径
    ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
    ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
    // False alarm, no strong reference path to GC Roots.
    if (result.leakingNode == null) {
      return noLeak(since(analysisStartNanoTime));
    }
    listener.onProgressUpdate(BUILDING_LEAK_TRACE);
    // 构建 LeakTrace
    LeakTrace leakTrace = buildLeakTrace(result.leakingNode);
    String className = leakingRef.getClassObj().getClassName();
    // 计算 retained size
    long retainedSize;
    if (computeRetainedSize) {

      listener.onProgressUpdate(COMPUTING_DOMINATORS);
      // Side effect: computes retained size.
      snapshot.computeDominators();

      Instance leakingInstance = result.leakingNode.instance;

      retainedSize = leakingInstance.getTotalRetainedSize();

      // TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
      if (SDK_INT <= N_MR1) {
        listener.onProgressUpdate(COMPUTING_BITMAP_SIZE);
        retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
      }
    } else {
      retainedSize = AnalysisResult.RETAINED_HEAP_SKIPPED;
    }
    // 构建最终的结果 AnalysisResult
    return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
        since(analysisStartNanoTime));
  }

这里主要的是 4 个步骤,寻找最短路径,计算 retained size,构建 LeakTrace 以及构建 AnalysisResult。这些都是 haha 库对其是如何解析的,这里就不再继续分析下去了。因为这里到了另一个大的话题,需要虚拟机相关的知识,尤其是 heap 管理这一块,以及需要熟知 hprof 协议。所以,这里更详细的分析将放在下一篇分析 haha 库相关的文章里面去。

四、总结

(1) 从大的层面上来看,LeakCanary 的核心包括了用于监测内存泄漏的 leakcanary-watcher 以及用于分析 hprof 的 leakcanary-analyzer 两个大的模块。leakcanary-watcher 中的核心类是 RefWatcher ,而 leakcanary-analyzer 中的核心类是 HeapAnalyzer。对于应用开发者来说,掌握 leakcanary-watcher 的原理会更适用一些。

(2) leakcanary-watcher 监测内存的核心原理是,监测 Activity/Fragment 组件的生命周期,组件在进入 onDestroy 后,一般的 framework 会主动 gc 一次,如果没有则还会强制 gc 一次。同时,还利用了弱引用在一次 gc 后便会进入到内存回收的状态的原理从而判断当前被监测的组件是否发生了内存泄漏。

(3) leakcanary-analyzer 负责 hprof 的分析,这个模块其实只是对 haha 库的所提供的 api 的应用,就像我们拿着 leakcanary 编写自己的业务代码一样。这里不作深入的分析,而是放到下一篇 haha 库相关的文章里再来详细分析。

最后,感谢你能读到并读完此文章,如果分析的过程中存在错误或者疑问都欢迎留言讨论。如果我的分享能够帮助到你,还请记得帮忙点个赞吧,谢谢。