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检测内存泄漏的流程。
现在我们结合具体代码来分析一下上面的流程:
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草图: