LeakCanary 源码解析

LeakCanary 是一个可以用于对内存泄漏进行检测的工具,它由 square 出品,今天就来对它进行一次源码解析。

装载

我们可以通过 LeakCanary.install 方法对 LeakCanary 进行装载:

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

其中 refWatcher 方法会获取到一个 AndroidRefWatcherBuilder 对象,之后通过设置其 listenerServiceClass 以及 excludedRefs 后,调用了 buildAndInstall 进行装载:

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) {
      ActivityRefWatcher.install(context, refWatcher);
    }
    if (watchFragments) {
      FragmentRefWatcher.Helper.install(context, refWatcher);
    }
  }
  LeakCanaryInternals.installedRefWatcher = refWatcher;
  return refWatcher;
}

这里首先调用了 build 方法进行了 RefWatcher 的构建,之后对于 Actvitiy 和 Fragment,分别通过 ActivityRefWatcherFragmentRefWatcherinstall 方法进行装载。

Activity 装载

我们先看到 ActvitiyRefWatcher.install 方法:

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

这里实际上是通过了 Application.registerActivityLifecycleCallbacks 方法来对 Activity 的 onDestroy 进行 hook,从而实现对引用回收的。

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

这里调用到了 refWatcher.watch,并传入了要销毁的 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);
}

可以看到,这里基于该传入的对象生成了其对应的 UUID,并创建了一个针对它的弱引用,其制定了 ReferenceQueueRefWatcher 中的 queue。

最后,通过 ensureGoneAsync 方法去确保了 Activity 的引用有被回收

Fragment 装载

接着我们看看 FragmentWatcher.Helper.install

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

  if (SDK_INT >= O) {
    fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
  }

  try {
    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();
  application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
}

这里构建了 FragmentRefWatcher 并添加进了 fragmentRefWatchers 列表,之后通过 Application.registerActivityLifecycleCallbacks 对 Activity 的 onCreate 进行了监听:

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

这里调用了 watcher.watchFragments 方法并传入了 Activity 从而对 Fragment 的生命周期进行监听:

@Override public void watchFragments(Activity activity) {
    if (activity instanceof FragmentActivity) {
      FragmentManager supportFragmentManager =
          ((FragmentActivity) activity).getSupportFragmentManager();
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
    }
  }

实际上是通过 Activity 获取到了 FragmentManager,之后调用了 Fragment.registerFragmentLifecycleCallbacks 方法:

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

这里实际上主要是对 Fragment 的 onDestroyView 以及 onDestroy 进行监听,都调用了 refWatcher.watch 从而建立其对应的弱引用并被 ReferenceQueue 监听。

检测内存泄漏

RefWatcher.watch 中,会调用到 ensureGoneAsync 方法来确保内存正常被回收:

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

这里通过 watchExecutor 执行了一个 ensureGone 任务,它负责了对 Activity 是否正常被回收的检测。

这个 WatchExecutor 的内部实际上是通过 HandlerThread 实现的,它内部通过 Handler 的 IdleHandler 机制来实现对 Activity 引用的检测,避免了对其他任务的影响。

之后我们看到 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;
  }
  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;
}

这里首先调用了 removeWeaklyReachableReferences 方法,它会将 ReferenceQueue 中的引用对应的 key 从 retainedKeys 中移除,之后它会调用 gone 方法判断引用是否已经被回收,如果已经被回收不用继续,若还未回收会调用 System.gc 建议 JVM 进行一次 GC,之后继续调用 removeWeaklyReachableReferences 方法后调用 gone 方法判断 GC 后是否被回收。若还未被回收说明发生了内存泄漏。

我们先看到 removeWeaklyReachableReferences 方法:

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 中拿出弱引用,并将其对应的 key 从 retainedKeys 中移除。

之后我们看到 gone 方法:

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

该方法会判断对应引用的 key 是否存在 retainedKeys 中,若不存在则说明发生了内存泄漏。这是什么原理呢?

因为在弱引用的机制中,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中,那么这些弱引用对应的 key 就会在前面的 removeWeaklyReachableReferences 方法中被移除。

当发生了内存泄漏后,会通过 HeapDumper.dumpHeap 获取到对应的 heapDumpFile,之后构建对应的 HeapDump 对象,并调用 heapdumpListener.analyze 对其进行分析。它会启动一个 HeapAnalyzeService 对文件进行分析。这个过程会通过对 hprof 文件的分析完成。LeakCanary 基于 haha 这个开源库实现了该功能。

本文链接:

https://blog.n0texpecterr0r.cn/index.php/archives/51/
1 + 5 =
快来做第一个评论的人吧~