LeakCanary详解(源码分析)

        LeakCanary目前已经成为我们在开发阶段查找内存泄漏的一个关键手段了,主要是因为LeakCanary的便捷性,我们不用再经常费劲的去看mat了。

       LeakCanary在检测内存泄漏方面的一个重要原理其实就是(ReferenceQueue),下面我们来介绍一下ReferenceQueue。

ReferenceQueue

当系统检测到与ReferenceQueue有关联的对象被回收后,会把对象的引用加入到ReferenceQueue队列中。举个例子:

当我们想检测一个对象是否被回收了,那么我们就可以采用 Reference + ReferenceQueue,需要几个步骤:

1、创建一个引用队列 ReferenceQueue。
2、创建 refrence 对象,并关联引用队列 ReferenceQueue。
3、在 reference 被回收的时候,refrence 会被添加到 ReferenceQueue中。

ReferenceQueue<Object> queue = new ReferenceQueue<Object>() ;
Object o = new Object() ;
WeakReference<Object> refrence = new WeakReference<>(o , queue);
o = null ;
System.gc() ;
Thread.sleep(5000) ;
System.out.println(queue.poll()) ;

在了解了ReferenceQueue后,我们来看一下LeakCanary检测内存泄漏的流程。

LeakCanary详解(源码分析)

 

现在我们结合具体代码来分析一下上面的流程:

1、App.install()

LeakCanary.java
//这里的install主要是调用了refWatcher这个静态函数
public static @NonNull RefWatcher install(@NonNull Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
}

//这里返回一个AndroidRefWatcherBuilder 
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
    return new AndroidRefWatcherBuilder(context);
}


AndroidRefWatchBuilder.java
//ok,其实refWatcher这个静态函数最终要返回的就是一个RefWatcher
  public @NonNull RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      if (enableDisplayLeakActivity) {
        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      }
      if (watchActivities) {
        //为Activity注册生命周期监听
        ActivityRefWatcher.install(context, refWatcher);
      }
      if (watchFragments) {
        //为Fragment注册生命周几监听
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

LeakCanary这个类相当于一个工厂类,对外封装了RefWatcher的构造方法。其中在buildAnInstall()里,我们会注册activity和fragment的生命周期监听(watchActivitys和watchFragments默认都是true,可以根据自己需要自行修改),下面我们来分析下activity的生命周期监听。

ActivityRefWatcher.java
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
    Application application = (Application) context.getApplicationContext();
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
    //通过Application的registerActivityLifecycleCallbacks
    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }

监控activity的生命周期是通过Application的registerActivityLifecycleCallbacks来实现的,registerActivityLifecycleCallbacks可以监控到activity的所有生命周期函数。

当activity的调用了onDestroy生命结束的时候,会出发onActivityDestroyed这个回调函数,这里我们看到了refWatcher.watch(activity)

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

refWatcher.watch()从这里开始,我们进入了真正的内存泄漏检测工作。前面所介绍的是对象的生命周期检测工作。当有对象的寿命周期结束的时候,我们就要检测它是否存在内存泄漏喽!

在watch中我们主要做三件事情:

1、生成一个UUID(唯一)

2、把UUID放到retainKeys中,retainKeys作为RefWatcher的成员变量,用来辅助检测对象是否存在泄漏,如果对象被回收会把对象的UUID从retainedKeys中移除(Set<String> retainedKeys)

3、创建一个弱引用,指向被监测的对象,并把弱引用和queue(ReferenceQueue)关联。关于ReferenceQueue请看文章前部有详细介绍。

4、判断reference是否被正确回收

RefWatcher.java
//watchedReference就是我们要监测的activity或者fragment了
public void watch(Object watchedReference, String referenceName) {
    .....
    //1、生命一个UUID
    String key = UUID.randomUUID().toString();
    //2、把UUID放到retainedKeys中
    retainedKeys.add(key);
    //3、创建弱引用,指向检测的对象
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    //4、判断reference是否被正确回收
    ensureGoneAsync(watchStartNanoTime, reference);
  }

好了,到这里正如我们前面介绍的ReferenceQueue时候讲的,我们已经把一个弱引用和一个ReferenceQueue关联起来了。前面我们讲过,当引用对应的对象被回收后,这个引用会加入到对应的ReferenceQueue中。对应到我们这里就是这里如果reference指向的activity被回收后,refernce就会加入到queue中。

我们来继续分析上面的第4步(判断reference是否被正确回收)

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

    removeWeaklyReachableReferences();//下面分析

    if (debuggerControl.isDebuggerAttached()) {
      //排除debug的状况
      // The debugger can create false leaks.
      return RETRY;
    }
    if (gone(reference)) {
      //如果reference已经不在retainedKeys中了,说明被正确回收了
      return DONE;
    }
    gcTrigger.runGc();  //执行垃圾回收
    removeWeaklyReachableReferences();
    if (!gone(reference)) {
      //如果reference在retainKeys中,没被回收啊!兄嘚
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);  //本次gc时间?

      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 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.
    //当弱引用指向的对象被回收后,相应的弱引用会加入到queue中,这里把被回收的对象从retainedKeys中移除
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key);
    }
  }

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

这里我们先分析两个辅助方法:

1、boolean gone(KeyedWeakReference reference),这个方法比较简单,就是判断引用是否还在retainedKeys中。如果不在retainedKeys则返回true。

2、void removeWeaklyReachableReferences(),这个方法也比较简单,就是移除了queue队头的一个引用,然后把这个引用对应在retainedKey中的UUID移除。

有了上面的两个辅助函数,下面我们来继续分析ensureGone。

1、先进行了一些时间上的计算,这个可以自己分析下源码。

2、调用了一次removeWeaklyReachableReferences(),移除了queue队头的一个引用(如果队列不为空的情况)

3、调用gone(reference),判断reference是否还在retainedKey中,上面我们分析过retainedKey,这里可以看出如果调用了一次removeWeaklyReachableReferences后reference还在retainedKey中,说明对象没有被回收可能出现泄漏,如果已经不在retainedKey中,直接返回DONE。

4、gcTrigger.runGc(); //执行垃圾回收

5、重复(步骤2、3),执行了垃圾回收后再看看reference是否被回收。如果没被回收,对不起,可能泄漏了。

根据开始时候的流程介绍,现在我们要走入了heapdump的地盘。

6、File heapDumpFile = heapDumper.dumpHeap();这里开始,我们要去dump现在的内存情况了,其实就是生成一个hropf,使用过mat的同学肯定了解这个后缀的文件是可以查看内存中对象的引用链的。

7、内存dump结束后就是分析refrence的引用链了。heapdumpListener.analyze(heapDump)。

第6、7步这里没有具体的代码引用,喜欢的童鞋可以自己看下,代码在AndroidHeapDumper和HeapAnalyzerService中

其中dump内存使用的是Debug.dumpHprofData(heapDumpFile.getAbsolutePath());这个方法,二分析hropf的代码主要在analyzer这个moduler下面,分析的代码我并没有看,因为没有找到hropf的具体文件结构。以后找到了再继续分析下!

 

总结:

本文主要是分析了下LeakCanary的工作流程,其工作流程简单的抽象出来就是。

1、注册对象生命周期监听

2、利用ReferenceQueue检测对象是否正确回收(二次确认)

3、dump内存生成hropf文件

4、分析hropf文件,查找泄漏对象到gcroot的最短引用路径

 

最后附上一张LeakCanary的uml草图:

LeakCanary详解(源码分析)