一、引言
最近项目中需要对App的性能进行监控并采集性能数据,在内存泄漏的监控上,采用了LeakCanary的Sdk。
LeakCanary是业界公认的比较好的一个内存监控项目,很早以前就听说并使用过,但是一直没有去了解其背后的监控原理。今天就借这个项目的开发之际,好好的分析一下其原理。
二、原理分析
我们还是从LeakCanary的使用上入手,代码如下:
final RefWatcher refWatcher = LeakCanary.install(this);
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
...省略未实现的方法
@Override
public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
});
上面的代码中使用了LeakCanary.install得到了一个RefWatcher对象,我们进入到LeakCanary的install方法,代码如下:
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstalle();
}
这里使用了建造者模式,其中的listenerServiceClass
方法设置了内存泄漏处理的Service,excludedRefs
方法设置了不对哪些对象进行监听(这里可以设置Android系统的对象和第三方Sdk的对象),最后执行了buildAndInstalle方法生成了一个RefWatcher对象。
在生成了RefWatcher对象后,需要就去监听Activity是否存在内存泄漏。这里我们使用了registerActivityLifecycleCallbacks方法,并在ActivityLifecycleCallbacks接口的onActivityDestroyed方法中监听Activity对象,代码如下:
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
...省略未实现的方法
@Override
public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
});
可以看到,这里又调用了RefWatcher对象的watch方法,代码如下:
public void watch(Object watchedReference) {
this.watch(watchedReference, "");
}
watch方法又调用了watch的重载方法,代码如下:
public void watch(Object watchedReference, String referenceName) {
if (this != DISABLED) {
Preconditions.checkNotNull(watchedReference, "watchedReference");
Preconditions.checkNotNull(referenceName, "referenceName");
long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
this.retainedKeys.add(key);
KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, this.queue);
this.ensureGoneAsync(watchStartNanoTime, reference);
}
}
上面的代码判断了watchedReference对象和referenceName对象不为空,然后使用UUID.randomUUID().toString()创建了一个全局唯一key值。
对于UUID.randomUUID有疑问的可以看一下UUID.randomUUID()简单介绍 这篇文章
然后new了一个KeyedWeakReference对象,并将watchedReference对象、全局唯一的key值、引用对象的名字(这里为空)、ReferenceQueue对象传入到构造函数中。
而LeakCanary监控内存泄漏的关键就在于KeyedWeakReference这个对象。所以,我们进入它的构造函数中看一下,代码如下:
KeyedWeakReference(Object referent, String key, String name, ReferenceQueue
KeyedWeakReference的构造函数简单的调用了一下其父类WeakReference的构造方法,而其父类WeakReference又调用了其父类的Reference的构造方法。最终是将watchedReference对象和ReferenceQueue对象分别赋值给了Reference的reference和queue。
1、reference:
表示引用的对象,也就是上述我们所说的被监控对象。源码对其的描述为/* Treated specially by GC */
,表示该对象会被对象特殊处理。而对象被回收后,Reference对象的queue队列会将自己(this对象)进行入队操作。
2、queue:
当该对象中有值时,表示引用的对象reference已经被GC回收。根据这个原理,我们就可以判断那些本应该被回收的对象如果没有被回收,就判断为内存泄漏。
好了,让我们从KeyedWeakReference的构造函数回退到上面的watch方法,看看LeakCanary是怎么利用这个KeyedWeakReference的。watch方法最后调用了ensureGoneAsync方法,代码如下:
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
this.watchExecutor.execute(new Retryable() {
public Result run() {
return RefWatcher.this.ensureGone(reference, watchStartNanoTime);
}
});
}
ensureGoneAsync方法在线程池中执行了ensureGone方法,代码如下:
Result ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = TimeUnit.NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
this.removeWeaklyReachableReferences();
if (this.debuggerControl.isDebuggerAttached()) {
return Result.RETRY;
} else if (this.gone(reference)) {
return Result.DONE;
} else {
this.gcTrigger.runGc();
this.removeWeaklyReachableReferences();
if (!this.gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = TimeUnit.NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = this.heapDumper.dumpHeap();
if (heapDumpFile == HeapDumper.RETRY_LATER) {
return Result.RETRY;
}
long heapDumpDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
HeapDump heapDump = this.heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key).referenceName(reference.name).watchDurationMs(watchDurationMs).gcDurationMs(gcDurationMs).heapDumpDurationMs(heapDumpDurationMs).build();
this.heapdumpListener.analyze(heapDump);
}
return Result.DONE;
}
}
ensureGone方法执行了下面几个步骤:
1、首先调用了removeWeaklyReachableReferences方法,代码如下:
private void removeWeaklyReachableReferences() {
KeyedWeakReference ref;
while((ref = (KeyedWeakReference)this.queue.poll()) != null) {
this.retainedKeys.remove(ref.key);
}
}
该方法从queue进行出队列操作,queue.poll() != null
表明该引用对象被GC正常回收了,然后将该对象的标记key从retainedKeys中移除。
2、然后判断当前是否处于debug状态,如果是则返回,否则第一次执行gone方法,gone方法的代码如下:
private boolean gone(KeyedWeakReference reference) {
return !this.retainedKeys.contains(reference.key);
}
逻辑很简单,就是判断一下retainedKeys是否包含引用对象的标记key。如果不包含则表明,该对象被GC正常回收了,因此gone方法返回true,否则存在可疑的内存泄漏则返回false。
3、如果第2步返回为false,LeakCanary不会马上触发内存泄漏告警,而是调用了GcTrigger对象的runGc方法主动告知GC,现在应该要回收对象了。
4、调用完GcTrigger对象的runGc方法后,接着又一次调用了removeWeaklyReachableReferences方法,看该对象是否被回收了。
5、然后第二次调用gone方法,如果此时的gone方法还是返回false,则表示该对象存在内存泄漏了。
6、接下来就是将当前时刻的内存堆栈dump下来,并在DisplayLeakService进行分析,然后在DisplayLeakActivity中进行展示。
而对于解析内存堆栈和展示内存泄漏信息的逻辑本文就不做分析了,因为这不属于本文的范畴。有兴趣的同学可以自己读一下源码。
三、总结
LeakCanary内存泄漏的监控原理是利用了Reference对象中的引用对象被回收时,会将其自己存储到ReferenceQueue队列,然后判断ReferenceQueue队列是否有值,来判断对象是否内存泄漏的。
而源码中的WeakHashMap同样的利用了这个原理判断引用的对象是否被回收了,后面有时间可以看一下WeakHashMap的源码进行验证一下。