一步步拆解 LeakCanary

前言

内存泄露,一直是我们性能优化方面的重点。今天,就让我们一起来拆解 LeakCanary,一步步理解它的原理。

原理概览

讲解 LeakCannary 原理之前,我们先来说一下它的主要原理,给大家吃颗定心丸,其实挺简单的,大概可以分为以下几步:

  • 监听 Activity 的生命周期
  • 在 onDestroy 的时候,创建相应的 Refrence 和 RefrenceQueue,并启动后台进程去检测
  • 一段时间之后,从 RefrenceQueue 读取,若读取不到相应 activity 的 Refrence,有可能发生泄露了,这个时候,再促发 gc,一段时间之后,再去读取,若在从 RefrenceQueue 还是读取不到相应 activity 的 refrence,可以断定是发生内存泄露了
  • 发生内存泄露之后,dump,分析 hprof 文件,找到泄露路径(使用 haha 库分析),发送到通知栏

原理分析

LeakCanary#Install

public static RefWatcher install(Application application) {

return refWatcher(application).listenerServiceClass(DisplayLeakService.class)

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

.buildAndInstall();

}

listenerServiceClass 方法

public AndroidRefWatcherBuilder listenerServiceClass(

Class<? extends AbstractAnalysisResultService> listenerServiceClass) {

return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));

}

public final class ServiceHeapDumpListener implements HeapDump.Listener {

private final Context context;

private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;

public ServiceHeapDumpListener(Context context,

Class<? extends AbstractAnalysisResultService> listenerServiceClass) {

// 启动后台服务监听

setEnabled(context, listenerServiceClass, true);

// 启动 HeapAnalyzerService ,用来分析 dump 文件

setEnabled(context, HeapAnalyzerService.class, true);

this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");

this.context = checkNotNull(context, "context").getApplicationContext();

}

----

}

listenerServiceClass() 方法绑定了一个后台服务 DisplayLeakService,这个服务主要用来分析内存泄漏结果并发送通知。你可以继承并重写这个类来进行一些自定义操作,比如上传分析结果等。

RefWatcherBuilder.excludedRefs

public final T excludedRefs(ExcludedRefs excludedRefs) {

this.excludedRefs = excludedRefs;

return self();

}

AndroidExcludedRefs.java

/**

* This returns the references in the leak path that can be ignored for app developers. This

* doesn't mean there is no memory leak, to the contrary. However, some leaks are caused by bugs

* in AOSP or manufacturer forks of AOSP. In such cases, there is very little we can do as app

* developers except by resorting to serious hacks, so we remove the noise caused by those leaks.

*/

public static ExcludedRefs.Builder createAppDefaults() {

return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));

}

public static ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) {

ExcludedRefs.Builder excluded = ExcludedRefs.builder();

for (AndroidExcludedRefs ref : refs) {

if (ref.applies) {

ref.add(excluded);

((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());

}

}

return excluded;

}

excludedRefs() 方法定义了一些对于开发者可以忽略的路径,意思就是即使这里发生了内存泄漏,LeakCanary 也不会弹出通知。这大多是系统 Bug 导致的,无需用户进行处理。

AndroidRefWatcherBuilder.buildAndInstall

buildAndInstall 所做的工作,调用 build 构建 refWatcher,判断 refWatcher 是否 DISABLED,若不是 DISABLED 状态,调用 install 方法,并将 refWatcher 返回回去

/**

* Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).

*/

public RefWatcher buildAndInstall() {

// 构建 refWatcher 对象

RefWatcher refWatcher = build();

// 判断是否 DISABLED,若不是 DISABLED 状态,调用

if (refWatcher != DISABLED) {

LeakCanary.enableDisplayLeakActivity(context);

ActivityRefWatcher.install((Application) context, refWatcher);

}

return refWatcher;

}

了解 build 方法 之前,我们先来看一下 RefWatcherBuilder 是什么东东?

RefWatcherBuilder

public class RefWatcherBuilder<T extends RefWatcherBuilder<T>> {

private ExcludedRefs excludedRefs;

private HeapDump.Listener heapDumpListener;

private DebuggerControl debuggerControl;

private HeapDumper heapDumper;

private WatchExecutor watchExecutor;

private GcTrigger gcTrigger;

/** Creates a {@link RefWatcher}. */

public final RefWatcher build() {

if (isDisabled()) {

return RefWatcher.DISABLED;

}

ExcludedRefs excludedRefs = this.excludedRefs;

if (excludedRefs == null) {

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

}

return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,

excludedRefs);

}

build 方法看到这里你是不是有一种很眼熟的感觉,没错,它运用了建造者模式,与我们 Android 中的 AlertDialog.build 同出一辙。

RefWatcherBuilder 主要有几个重要的成员变量

  • watchExecutor : 线程控制器,在 onDestroy() 之后并且主线程空闲时执行内存泄漏检测
  • debuggerControl : 判断是否处于调试模式,调试模式中不会进行内存泄漏检测
  • gcTrigger : 用于 GC,watchExecutor 首次检测到可能的内存泄漏,会主动进行 GC,GC 之后会再检测一次,仍然泄漏的判定为内存泄漏,进行后续操作
  • heapDumper : dump 内存泄漏处的 heap 信息,写入 hprof 文件
  • heapDumpListener : 解析完 hprof 文件,进行回调,并通知 DisplayLeakService 弹出提醒
  • excludedRefs : 排除可以忽略的泄漏路径

接下来,我们一起来看一下 ActivityRefWatcher.install 方法

ActivityRefWatcher.install((Application) context, refWatcher);

public final class ActivityRefWatcher {

/** @deprecated Use {@link #install(Application, RefWatcher)}. */

@Deprecated

public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {

install(application, refWatcher);

}

public static void install(Application application, RefWatcher refWatcher) {

new ActivityRefWatcher(application, refWatcher).watchActivities();

}

private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =

new Application.ActivityLifecycleCallbacks() {

@Override

public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

}

@Override

public void onActivityStarted(Activity activity) {

}

@Override

public void onActivityResumed(Activity activity) {

}

@Override

public void onActivityPaused(Activity activity) {

}

@Override

public void onActivityStopped(Activity activity) {

}

@Override

public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

}

@Override

public void onActivityDestroyed(Activity activity) {

ActivityRefWatcher.this.onActivityDestroyed(activity);

}

};

private final Application application;

private final RefWatcher refWatcher;

/**

* Constructs an {@link ActivityRefWatcher} that will make sure the activities are not leaking

* after they have been destroyed.

*/

public ActivityRefWatcher(Application application, RefWatcher refWatcher) {

this.application = checkNotNull(application, "application");

this.refWatcher = checkNotNull(refWatcher, "refWatcher");

}

void onActivityDestroyed(Activity activity) {

refWatcher.watch(activity);

}

public void watchActivities() {

// Make sure you don't get installed twice.

stopWatchingActivities();

application.registerActivityLifecycleCallbacks(lifecycleCallbacks);

}

public void stopWatchingActivities() {

application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);

}

}

install 来说,主要做以下事情

  • 创建 ActivityRefWatcher,并调用 watchActivities 监听 activity 的生命周期
  • 在 activity 被销毁的时候,会回调 lifecycleCallbacks 的 onActivityDestroyed 方法,这时候会调用 onActivityDestroyed 去分析,而 onActivityDestroyed 方法又会回调 refWatcher.watch(activity)

我们回到 refWatcher.watch 方法

public void watch(Object watchedReference) {

watch(watchedReference, "");

}

/**

* Watches the provided references and checks if it can be GCed. This method is non blocking,

* the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed

* with.

*

* @param referenceName An logical identifier for the watched object.

*/

public void watch(Object watchedReference, String referenceName) {

if (this == DISABLED) {

return;

}

checkNotNull(watchedReference, "watchedReference");

checkNotNull(referenceName, "referenceName");

final long watchStartNanoTime = System.nanoTime();

// 保证 key 的唯一性

String key = UUID.randomUUID().toString();

// 添加到 set 集合中

retainedKeys.add(key);

// 穿件 KeyedWeakReference 对象

final KeyedWeakReference reference =

new KeyedWeakReference(watchedReference, key, referenceName, queue);

ensureGoneAsync(watchStartNanoTime, reference);

}

  • retainedKeys : 一个 Set 集合,每个检测的对象都对应着一个唯一的 key,存储在 retainedKeys 中
  • KeyedWeakReference : 自定义的弱引用,持有检测对象和对用的 key 值

我们先来看一下 KeyedWeakReference ,可以看到 KeyedWeakReference 继承于 WeakReference,并定义了 key,name 字段

final class KeyedWeakReference extends WeakReference<Object> {

public final String key;

public final String name;

KeyedWeakReference(Object referent, String key, String name,

ReferenceQueue<Object> referenceQueue) {

super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));

this.key = checkNotNull(key, "key");

this.name = checkNotNull(name, "name");

}

}

  • key 对应的 key 值名称
  • referenceQueue 引用队列,当结合 Refrence 使用的时候,垃圾回收器回收的时候,会把相应的对象加入到 refrenceQueue 中。

弱引用和引用队列 ReferenceQueue 联合使用时,如果弱引用持有的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。即 KeyedWeakReference 持有的 Activity 对象如果被垃圾回收,该对象就会加入到引用队列 queue 中。

ensureGoneAsync 方法

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {

watchExecutor.execute(new Retryable() {

@Override

public Retryable.Result run() {

return ensureGone(reference, watchStartNanoTime);

}

});

}

ensureGoneAsync 这个方法,在 watchExecutor 的回调里面执行了 ensureGone 方法,watchExecutor 是 AndroidWatchExecutor 的实例。

接下来,我们一起来看一下 watchExecutor,主要关注 execute 方法

watchExecutor

public final class AndroidWatchExecutor implements WatchExecutor {

static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";

private final Handler mainHandler;

private final Handler backgroundHandler;

private final long initialDelayMillis;

private final long maxBackoffFactor;

public AndroidWatchExecutor(long initialDelayMillis) {

mainHandler = new Handler(Looper.getMainLooper());

HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);

handlerThread.start();

backgroundHandler = new Handler(handlerThread.getLooper());

this.initialDelayMillis = initialDelayMillis;

maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;

}

@Override

public void execute(Retryable retryable) {

// 当前线程是主线程

if (Looper.getMainLooper().getThread() == Thread.currentThread()) {

waitForIdle(retryable, 0);

} else { // 当前线程不是主线程

postWaitForIdle(retryable, 0);

}

}

--------

}

execute 方法,首先判断是否是主线程,如果是主线程,调用 waitForIdle 方法,等待空闲的时候执行,如果不是主线程,调用 postWaitForIdle 方法。我们一起来看一下 postWaitForIdle 和 waitForIdle 方法。

// 调用 mainHandler 的 post 方法,,确保在主线程中执行

void postWaitForIdle(final Retryable retryable, final int failedAttempts) {

mainHandler.post(new Runnable() {

@Override

public void run() {

waitForIdle(retryable, failedAttempts);

}

});

}

// 当当前线程 looper 空闲的时候执行

void waitForIdle(final Retryable retryable, final int failedAttempts) {

// This needs to be called from the main thread.

// 当 looper 空闲的时候,会回调 queueIdle 方法

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {

@Override public boolean queueIdle() {

postToBackgroundWithDelay(retryable, failedAttempts);

return false;

}

});

}

可以看到 postWaitForIdle 方法其实是 调用 mainHandler 的 post 方法,,确保在主线程中执行,之后再 runnable 的 run 方法在调用 waitForIdle 方法。而 waitForIdle 方法是在等当前 looper 空闲之后,执行 postToBackgroundWithDelay 方法

void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {

// 取 Math.pow(2, failedAttempts), maxBackoffFactor 的最小值,maxBackoffFactor = Long.MAX_VALUE / 5,

// 第一次执行的时候 failedAttempts 是 0 ,所以 exponentialBackoffFactor 是1

long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);

// initialDelayMillis 的默认值是 5

long delayMillis = initialDelayMillis * exponentialBackoffFactor;

// 所以第一次延迟执行的时候是 5s,若

backgroundHandler.postDelayed(new Runnable() {

@Override public void run() {

Retryable.Result result = retryable.run();

// 过 result == RETRY,再次调用 postWaitForIdle,下一次的 delayMillis= 上一次的 delayMillis *2;

// 正常情况下,不会返回 RETRY,当 heapDumpFile == RETRY_LATER (即 dump heap 失败的时候),会返回 RETRY

if (result == RETRY) {

postWaitForIdle(retryable, failedAttempts + 1);

}

}

}, delayMillis);

}

postToBackgroundWithDelay 方法有点类似递归,正常情况下,若 retryable.run() 返回的结果不等于 RETRY,只会执行一次。若 retryable.run() 返回 RETRY,则会执行多次,退出的条件是 retryable.run() 返回结果不等于 RETRY;

delay 的时间 取 Math.pow(2, failedAttempts), maxBackoffFactor 两个数的最小值,maxBackoffFactor = Long.MAX_VALUE / 5,而,第一次执行的时候 failedAttempts 是 0 ,所以 exponentialBackoffFactor 是 1,即 delayMillis = initialDelayMillis * exponentialBackoffFactor= 5*1=5;

因此,综合上面的例子,第一次执行的时间是 activity destroy 之后 5s。

OK,我们回到 ensureGone 方法,这才是我们的重点

@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;

}

// 判断 reference,即 activity 是否内回收了,若被回收了,直接返回

if (gone(reference)) {

return DONE;

}

// 调用 gc 方法进行垃圾回收

gcTrigger.runGc();

// 移除已经被回收的引用

removeWeaklyReachableReferences();

// activity 还没有被回收,证明发生内存泄露

if (!gone(reference)) {

long startDumpHeap = System.nanoTime();

long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

// dump heap,并生成相应的 hprof 文件

File heapDumpFile = heapDumper.dumpHeap();

if (heapDumpFile == RETRY_LATER) {// dump the heap 失败的时候

// Could not dump the heap.

return RETRY;

}

long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);

// 分析 hprof 文件

heapdumpListener.analyze(

new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,

gcDurationMs, heapDumpDurationMs));

}

return DONE;

}

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;

// 遍历 queue ,并从 retainedKeys set 集合中移除

while ((ref = (KeyedWeakReference) queue.poll()) != null) {

retainedKeys.remove(ref.key);

}

}

gone(reference) 方法,判断 retainedKeys set 集合,是否还含有 reference,若没有,证明已经被回收了;若含有,可能已经发生内存泄露。因为我们知道 refrence 被回收的时候,会被加进 queue 里面,值调用 gone 方法判断的时候,我们已经遍历 queue 移除掉 retainedKeys 里面的 refrence,若含有,证明 refrence 没有被回收,之所以说可能发生内存泄露,是因为 gc 回收器可能还没有回收。

private boolean gone(KeyedWeakReference reference) {

return !retainedKeys.contains(reference.key);

}

gcTrigger.runGc() 的主要作用是促发 gc,进行回收。

GcTrigger DEFAULT = new GcTrigger() {

@Override

public void runGc() {

// Code taken from AOSP FinalizationTest:

// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/

// java/lang/ref/FinalizationTester.java

// System.gc() does not garbage collect every time. Runtime.gc() is

// more likely to perfom a gc.

Runtime.getRuntime().gc();

enqueueReferences();

System.runFinalization();

}

private void enqueueReferences() {

// Hack. We don't have a programmatic way to wait for the reference queue daemon to move

// references to the appropriate queues.

try {

Thread.sleep(100);

} catch (InterruptedException e) {

throw new AssertionError();

}

}

};

ok,我们在回到 ensureGoneAsync 方法,整理一下它的流程

  • Activity onDestroy 5s 之后,检测 activity 的弱引用 refrence 有没有被回收,若被回收,证明没有发生内存泄露,若没有被回收,继续下面流程
  • 调用 gcTrigger.runGc() 促发垃圾回收机器进行回收
  • 再次检测 activity 的弱引用 refrence 有没有被回收,若被回收,证明没有发生内存泄露,若没有被回收,则认为发生内存泄露
  • dump heap,生成 hprof。
  • 分析 hprof 文件,找到泄露路径,发送到通知栏

关于如何 dump 和 如何解析hprof

  • 关于如何 dump

这里主要是调用 AndroidHeapDumper 的 dumpHeap 方法,而里面比较重要的是调用 Debug.dumpHprofData 生成 hprof 文件。

AndroidHeapDumper#dumpHeap

@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.

@Override

public File dumpHeap() {

File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();

if (heapDumpFile == RETRY_LATER) {

return RETRY_LATER;

}

FutureResult<Toast> waitingForToast = new FutureResult<>();

showToast(waitingForToast);

if (!waitingForToast.wait(5, SECONDS)) {

CanaryLog.d("Did not dump heap, too much time waiting for Toast.");

return RETRY_LATER;

}

Toast toast = waitingForToast.get();

try {

Debug.dumpHprofData(heapDumpFile.getAbsolutePath());

cancelToast(toast);

return heapDumpFile;

} catch (Exception e) {

CanaryLog.d(e, "Could not dump heap");

// Abort heap dump

return RETRY_LATER;

}

}

  • 如何解析hprof

当发生了泄漏就会生成 HeapDump 对象然后就会进入下面这个方法去启动 HeapAnalyzerServiceService 来进行分析

@Override

public void analyze(HeapDump heapDump) {

checkNotNull(heapDump, "heapDump");

HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);

}

关于如解析 hprof,请自行了解 haha 库的用法即原理,地址如下:

https://github.com/square/haha

public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {

long analysisStartNanoTime = System.nanoTime();

if (!heapDumpFile.exists()) {

Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);

return failure(exception, since(analysisStartNanoTime));

}

try {

HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);

HprofParser parser = new HprofParser(buffer);

Snapshot snapshot = parser.parse();

deduplicateGcRoots(snapshot);

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

} catch (Throwable e) {

return failure(e, since(analysisStartNanoTime));

}

}

经过解析之后机会把数据传递到 DisplayLeakService ,Service 会根据传入进来的数据发送通知栏通知,当你点击对应的通知进入DisplayLeakActivity界面就能显示泄漏日志了。

总结

LeakCanary 的原理总结如下

  • 监听 Activity 的生命周期
  • 在 onDestroy 的时候,创建相应的 Refrence 和 RefrenceQueue,并启动后台进程去检测
  • 一段时间之后,从 RefrenceQueue 读取,若读取不到相应 activity 的 Refrence,有可能发生泄露了,这个时候,再促发 gc,一段时间之后,再去读取,若在从 RefrenceQueue 还是读取不到相应 activity 的 refrence,可以断定是发生内存泄露了
  • 发生内存泄露之后,dump,分析 hprof 文件,找到泄露路径(使用 haha 库分析)

其中,比较重要的是如何确定是否发生内存泄露,而如何确定发生内存泄露最主要的原理是通过 Refrence 和 RefrenceQueue。悄悄地提醒你一下,面试必备。

最后,用一张图片来表示 leakCannary 的执行流程。

一步步拆解 LeakCanary

相关推荐