LeakCanary是很早之前的大名鼎鼎的square推出的一款安卓排查内存泄漏的工具。其功嫩相当强大,还记得19年的时候刚刚接触这款工具时,我们之前花了一整天找到的内存泄漏问题,用这款工具只用了大约十分钟就顺利的定位到了问题原因。
LeakCanary主要分为两块内容,第一块是发现排查内存泄漏问题,第二块是根据内存的状态输出泄漏的堆栈。本文主要讲解第一块内容,后续有时间会把第二块内容也补上。
PS:目前LeakCanary已经更新到2.8.1的版本,但是新版本代码结构较为复杂。所以本文还是机基于1.6.3的源码进行分析。
项目地址:https://github.com/square/leakcanary
LeakCanary的核心原理是在application中,注册所有activity的生命周期回调。当一个activity调用destory方法的时候,生成一个弱引用对象,然后加入到ReferenceQueue中。当一个对象如果可以被释放,那么其弱引用对象就会加入到ReferenceQueue中。
我们只要多次检查ReferenceQueue中是否存在这个activity的弱引用对象,就可以知道这个activity是否被泄漏了。
其实LeakCanary有两个实现类,第一个在leakcanary-android的library中,第二个在leakcanary-android-no-op的library中,区别很简单。第二个里面是空实现,LeakCanary是不推荐线上使用的,所以写了一个空实现,release包时使用。
protected void setupLeakCanary() {
enabledStrictMode();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
}
我们项目中有可能有多个进程,我们只要对对主进程进行检查即可。
1.上述调用install后,会调用到AndroidRefWatcherBuilder的buildAndInstall()方法。
这里有两个参数watchActivities和watchFragments,分别对activity和fragment进行泄漏检查。
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;
}
2.这里以Activity的注册为例,ActivityRefWatcher.install方法中,注册了所有activity的生命周期回调。
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);
}
1.首先,会通过RefWatcher的watch方法开启检查。
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
2.watch方法中,首先会针对activity生成一个弱引用对象。key值是随机生成的,主要是为了识别唯一的一个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);
}
3.ensureGoneAsync中,利用线程池中的线程,开启对内存泄漏的检查。这也是自然,毕竟不能在主线程进行检查。
这里的watchExecutor可以由外部传入,经典的组合模式。watchExecutor的不同,最终的组合逻辑就有区别了。可以满足不同的场景需求。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
4.ensureGone方法是整篇文章的核心。所以这里会拆成好多步讲解
@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
...
removeWeaklyReachableReferences();//第一步
...
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;
}
5.第一步,removeWeaklyReachableReferences方法。前文讲到了ReferenceQueue的属性,一个弱引用对象如果可以被释放,那么就会加入到这个队列中。我们遍历queue队列,如果存在,则加入到retainedKeys这个HashSet中。retainedKeys中保存了所有符合被释放条件的activity的弱引用对象的key。
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);
}
}
6.第二步,使用gone方法检查是否符合被释放条件。
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
7.第三步,尝试同志虚拟机进行一次垃圾回收操作。
我们知道,Java是由虚拟机控制的回收机制,所以一个对象复合了回收条件后,并不一定是会被立马释放掉的,同样的,也不会立马去检查是否符合释放条件。所以这时候就需要我们主动通知一下虚拟机去进行对应的操作。
这里也是经典的组合模式,触发runGc的对象也是可以被传入的。
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 perform a gc.
Runtime.getRuntime().gc();
enqueueReferences();
System.runFinalization();
}
Runtime.getRuntime().gc();//是通知虚拟机进行gc操作的。
enqueueReferences方法其实只是sleep了当前线程100毫秒,毕竟垃圾回收不是一下子就可以完成的。
System.runFinalization(); //强制调用已经失去引用的对象的finalize方法
8.第四步,再次调用removeWeaklyReachableReferences方法检查是否符合释放条件。
9.第五步,再次调用gone进行判断。
10.第六步,如果这时候还没有符合被释放的条件,那么就说明这个activity有可能被泄漏了,这时候就要获取一下当前的内存堆栈信息,调用分析工具进行分析了。