本文首要内容

  • 1、Reference 简介
  • 2、LeakCanary 运用
  • 3、LeakCanary 源码剖析

LeakCanary ,一种常见的内存走漏剖析东西,它能剖析出内存走漏点并以告知方式告知运用者,运用也比较简单,但功能强大。

笔者第一次见到 LeakCanary 时,并不清楚它的原理,了解到它仅仅一个开源工程并不是官方东西之后,觉得作者太牛逼了,内存走漏都能够检测。今天咱们一起来看 LeakCanary 的源码,揭开它的奥秘面纱。

1、Reference 简介

java中存在四种引证,从头温习一遍四种引证的用法及作用:

  • 强引证:最遍及的引证,声明一个变量便是强引证,比如 obj ,当它被置为null时,该目标可能会被 JVM 收回,由于还要看是否有其它强引证指向它。

    Object obj = new Object();
    
  • SoftReference,软引证:当内存不够用的时分,才会收回软引证

  • WeakReference,弱引证: new出来的目标没有强引证衔接时,下一次GC时,就会收回该目标。

  • PhantomReference,虚引证 : 与要与ReferenceQueue配合运用,它的get()办法永远回来null

SoftReference、WeakReference、PhantomReference等类都位于 java.lang.ref 包中,它们都有一个共同的父类,Reference 。

java.lang.ref包下首要都是reference相关的类,首要包括:

  • FinalReference: 代表强引证,使无法直接运用。
  • Finalizer:FinalReference的子类,首要处理finalize相关的工作
  • PhantomReference: 虚引证
  • Reference: 引证基类,abstract的
  • ReferenceQueue: 引证轨道行列
  • SoftReference:软引证
  • WeakedReference: 弱引证

下面,阐述关于Reference 相关的一个结论:

Reference引证的目标被收回时,Reference 目标将被增加到 ReferenceQueue中,前提是结构 Reference 时,参数中有 ReferenceQueue。

假如要监听某个目标是否被收回,有什么办法呢?

Object obj = new Object();
ReferenceQueue<Object> queue = new ReferenceQueue<>();
WeakedReference  r = new WeakedReference(ojb, queue);

依据上面的结论,假如 obj 目标被收回了,那么 queue 将增加 r,那么咱们能够查找行列,假如有r,则证明 obj 目标被收回了,监控完成。

检查下 Reference 的源码,它里面的要害代码如下:

// 静态变量,pending 
private static Reference pending = null;
// 线程,不停地收回 pending 
private static class ReferenceHandler extends Thread {
    ReferenceHandler(ThreadGroup g, String name) {
        super(g, name);
    }
    public void run() {
        for (;;) {
            Reference r;
            synchronized (lock) {
                if (pending != null) {
                    r = pending;
                    Reference rn = r.next;
                    pending = (rn == r) ? null : rn;
                    r.next = r;
                } else {
                    try {
                        lock.wait();
                    } catch (InterruptedException x) { }
                    continue;
                }
            }
            // Fast path for cleaners
            if (r instanceof Cleaner) {
                ((Cleaner)r).clean();
                continue;
            }
            // 将 r 增加到 ReferenceQueue 中
            ReferenceQueue q = r.queue;
            if (q != ReferenceQueue.NULL) q.enqueue(r);
        }
    }
}

看到这里,可能有同学会发问,pending 在哪被赋值的?怎样知道它便是要被收回的 Reference 呢?确实,这个问题我也没有从源码中查到,从网上找的材料来看,都说 pending 由 JVM 保护。Reference 有个成员变量next,它能够很轻松地变成一个链表,而pending 正是一个链表的头节点,假如某个 Reference 将被收回,它将被 增加到pending 的链表当中,假如它立刻要被收回了,ReferenceHandler 线程将它增加到 ReferenceQueue 中。

经过 Reference 的学习,感觉 LeakCanary 最大的难题处理了,如何判定一个目标是否被收回了。

2、LeakCanary 运用

LeakCanary 运用十分简单,首要在你项目app下的build.gradle中装备:

dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
  releaseImplementation   'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
  // 可选,假如你运用支持库的fragments的话
  debugImplementation   'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'
}

然后在你的Application中装备:

public class WanAndroidApp extends Application {
@Override public void onCreate() {
  super.onCreate();
  if (LeakCanary.isInAnalyzerProcess(this)) {
    // 1
    return;
  }
  // 2
  refWatcher = LeakCanary.install(this);
}

运用便是这么得简单。

怎样判别一个目标已死呢?内存可达性算法,即从一个根目标动身,假如无法寻到一条途径指向该目标,则目标已死。

假设是咱们自己来设计 LeakCanary ,咱们会怎样去设计呢?现在现已能够监控某个目标是否现已被收回了。咱们也不可能去监听一切的目标吧,这样不现实,肯定是去找特定目标来监控,在Android中,常见的内存走漏都会导致 activity 无法被收回,activity 便是最特定的目标,所以,能够监听 activity。

LeakCanary 正是这样设计的,它现在能够监听 activity 和 fragment。

3、LeakCanary 源码剖析

先看看 install 办法:

public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
    .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
    .buildAndInstall();
  }

install 办法回来 RefWatcher 目标,这是一个链式调用,咱们一步步地来看。

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

refWatcher 办法回来 AndroidRefWatcherBuilder 目标,从姓名可知它是一个结构器,builder,它的结构函数也很简单,保存context

AndroidRefWatcherBuilder(@NonNull Context context) {
this.context = context.getApplicationContext();
}

持续回到 install 办法,检查 listenerServiceClass 办法:

public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
  @NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
return self();
}

AndroidRefWatcherBuilder 承继自 RefWatcherBuilder ,上面的代码便是为 AndroidRefWatcherBuilder 赋值一个成员变量,heapDumpListener 。持续回到 install 办法

excludedRefs(AndroidExcludedRefs.createAppDefaults().build())

这句话的意思是,扫除各种现已的不是内存走漏的情况。其实为什么要在第一步中指出,这是一个 Builder呢,咱们常见的 builder 代码里,有各式各样的赋值,但最要害的代码往往仅仅它的 build 办法,咱们别被这种链式调用弄晕了,这些不重要,别死抠细节不放,大约理解意思就行,接下来咱们看最重要的办法:

public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
  throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
//调用build办法,构建 RefWatcher 
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
  if (enableDisplayLeakActivity) {
    LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
  }
  if (watchActivities) {
    //监听activity
    ActivityRefWatcher.install(context, refWatcher);
  }
    //监听 fragment
  if (watchFragments) {
    FragmentRefWatcher.Helper.install(context, refWatcher);
  }
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}

build 办法回来 RefWatcher ,比较简单,其实便是将之前链式调用赋值的各个目标,赋值给 RefWatcher

public final RefWatcher build() {
if (isDisabled()) {
  return RefWatcher.DISABLED;
}
if (heapDumpBuilder.excludedRefs == null) {
  heapDumpBuilder.excludedRefs(defaultExcludedRefs());
}
HeapDump.Listener heapDumpListener = this.heapDumpListener;
if (heapDumpListener == null) {
  heapDumpListener = defaultHeapDumpListener();
}
DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
  debuggerControl = defaultDebuggerControl();
}
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
  heapDumper = defaultHeapDumper();
}
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
  watchExecutor = defaultWatchExecutor();
}
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);
}

最最要害的还得是 install 办法,本文中咱们只剖析 activity 的逻辑,fragment 暂不剖析。

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

install 办法中只有三行,第一步,获取 Application 目标,第二步,生成一个 ActivityRefWatcher 目标,第三步,看办法名,貌似是注册了一个监听,一个activity生命周期的监听。看看 lifecycleCallbacks 这个回调目标:

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

要害代码终于出现,当activity 调用 onDestroyed 办法时,会调用lifecycleCallbacks 中的办法,然后能够拿到 activity 的引证。结合之前 Reference 的剖析,要监听目标是否被收回,首要得拿到它的引证,现在 activity 的引证拿到了。持续往下剖析。

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

经过拿到的 activity 引证,结构 KeyedWeakReference 目标,其实它承继自 WeakReference ,它是一个弱引证。构建弱引证的一起,在结构函数中增加 ReferenceQueue,当 activity 被收回时,KeyedWeakReference 目标会被增加到ReferenceQueue当中。假如出现内存走漏,则 ReferenceQueue 找不到对应的 KeyedWeakReference 目标,那么就能够判别产生内存走漏了。

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

ensureGoneAsync办法中,经过 watchExecutor 履行一个 run 办法,watchExecutor 只会在主线程中履行它,持续检查 ensureGone办法

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;
}
//gone办法便是检查到 activity 现已被收回,则回来 DONE,标明内存无漏泄
if (gone(reference)) {
  return DONE;
}
//调用 gc
gcTrigger.runGc();
removeWeaklyReachableReferences();
//调用gc之后,再次检查 ,假如 activity 还没被收回,则是有内存走漏了
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();
  // 剖析 heap 的prof文件,找出内存走漏点
  heapdumpListener.analyze(heapDump);
}
return DONE;
}

假如 activity 现已被收回,那么 ReferenceQueue 中将增加指向它的 Reference,这是一条大原则,一定要记住。在 watch 办法时,为每个 activity结构对应的Reference时,还增加了一个key,并把key增加到一个set当中。

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

遍历 ReferenceQueue 中得到的Reference,拿到 Reference,一起删除 set 中对应的key。所以,set中不包括某个 key,则阐明对应的 activity现已被收回,反之则是没有收回。

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

gone办法正好验证这点,假如gone回来为true,那么整个进程也结束了。假如gone回来为false,则标明可能有内存走漏,所以履行一次gc之后 ,再次调用gone办法,检查是否有无走漏。假如还有走漏,则剖析生成的prof文件,找出要害的走漏途径。关于如何剖析 prof 文件,此处不展开了,其实 LeakCanary 也是借用其它的开源库来剖析的。

最后总结下整个进程:

在一个Activity履行完onDestroy()之后,将它放入WeakReference中,然后将这个WeakReference类型的Activity目标与ReferenceQueque关联。这时再从ReferenceQueque中检查是否有没有该目标,假如没有,履行gc,再次检查,还是没有的话则判别产生内存泄露了。最后用HAHA这个开源库去剖析dump之后的heap内存。