本文主要内容
- 1、Reference 简介
- 2、LeakCanary 使用
- 3、LeakCanary 源码分析
LeakCanary ,一种常见的内存泄漏分析工具,它能分析出内存泄漏点并以通知形式告诉使用者,使用也比较简单,但功能强大。
笔者第一次见到 LeakCanary 时,并不清楚它的原理,了解到它只是一个开源工程并不是官方工具之后,觉得作者太牛逼了,内存泄漏都可以检测。今天我们一起来看 LeakCanary 的源码,揭开它的神秘面纱。
1、Reference 简介
java中存在四种引用,重新温习一遍四种引用的用法及作用:
-
强引用:最普遍的引用,声明一个变量就是强引用,比如 obj ,当它被置为null时,该对象可能会被 JVM 回收,因为还要看是否有其它强引用指向它。
Object obj = new Object();
-
SoftReference,软引用:当内存不够用的时候,才会回收软引用
-
WeakReference,弱引用: new出来的对象没有强引用连接时,下一次GC时,就会回收该对象。
-
PhantomReference,虚引用 : 与要与ReferenceQueue配合使用,它的get()方法永远返回null
SoftReference、WeakReference、PhantomReference等类都位于 java.lang.ref 包中,它们都有一个共同的父类,Reference 。
java.lang.ref包下主要都是reference相关的类,主要包括:
- FinalReference: 代表强引用,使没法直接使用。
- Finalizer:FinalReference的子类,主要处理finalize相关的工作
- PhantomReference: 虚引用
- Reference: 引用基类,abstract的
- ReferenceQueue: 引用轨迹队列
- SoftReference:软引用
- WeakedReference: 弱引用
下面,阐述关于Reference 相关的一个结论:
Reference引用的对象被回收时,Reference 对象将被添加到 ReferenceQueue中,前提是构造 Reference 时,参数中有 ReferenceQueue。
如果要监听某个对象是否被回收,有什么办法呢?
Object obj = new Object();
ReferenceQueue<Object> queue = new ReferenceQueue<>();
WeakedReference r = new WeakedReference(ojb, queue);
根据上面的结论,如果 obj 对象被回收了,那么 queue 将添加 r,那么我们可以查找队列,如果有r,则证明 obj 对象被回收了,监控完成。
查看下 Reference 的源码,它里边的关键代码如下:
// 静态变量,pending
private static Reference pending = null;
// 线程,不停地回收 pending
private static class ReferenceHandler extends Thread {
ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}
public void run() {
for (;;) {
Reference r;
synchronized (lock) {
if (pending != null) {
r = pending;
Reference rn = r.next;
pending = (rn == r) ? null : rn;
r.next = r;
} else {
try {
lock.wait();
} catch (InterruptedException x) { }
continue;
}
}
// Fast path for cleaners
if (r instanceof Cleaner) {
((Cleaner)r).clean();
continue;
}
// 将 r 添加到 ReferenceQueue 中
ReferenceQueue q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
}
}
}
看到这里,可能有同学会提问,pending 在哪被赋值的?怎么知道它就是要被回收的 Reference 呢?确实,这个问题我也没有从源码中查到,从网上找的资料来看,都说 pending 由 JVM 维护。Reference 有个成员变量next,它可以很轻松地变成一个链表,而pending 正是一个链表的头节点,如果某个 Reference 将被回收,它将被 添加到pending 的链表当中,如果它马上要被回收了,ReferenceHandler 线程将它添加到 ReferenceQueue 中。
通过 Reference 的学习,感觉 LeakCanary 最大的难题解决了,如何判定一个对象是否被回收了。
2、LeakCanary 使用
LeakCanary 使用非常简单,首先在你项目app下的build.gradle中配置:
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
// 可选,如果你使用支持库的fragments的话
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'
}
然后在你的Application中配置:
public class WanAndroidApp extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// 1
return;
}
// 2
refWatcher = LeakCanary.install(this);
}
使用就是这么得简单。
怎么判断一个对象已死呢?内存可达性算法,即从一个根对象出发,如果无法寻到一条路径指向该对象,则对象已死。
假设是我们自己来设计 LeakCanary ,我们会怎么去设计呢?目前已经可以监控某个对象是否已经被回收了。我们也不可能去监听所有的对象吧,这样不现实,肯定是去找特定对象来监控,在Android中,常见的内存泄漏都会导致 activity 无法被回收,activity 就是最特定的对象,所以,可以监听 activity。
LeakCanary 正是这样设计的,它目前可以监听 activity 和 fragment。
3、LeakCanary 源码分析
先看看 install 方法:
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
install 方法返回 RefWatcher 对象,这是一个链式调用,我们一步步地来看。
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
return new AndroidRefWatcherBuilder(context);
}
refWatcher 方法返回 AndroidRefWatcherBuilder 对象,从名字可知它是一个构造器,builder,它的构造函数也很简单,保存context
AndroidRefWatcherBuilder(@NonNull Context context) {
this.context = context.getApplicationContext();
}
继续回到 install 方法,查看 listenerServiceClass 方法:
public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
@NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
return self();
}
AndroidRefWatcherBuilder 继承自 RefWatcherBuilder ,上面的代码就是为 AndroidRefWatcherBuilder 赋值一个成员变量,heapDumpListener 。继续回到 install 方法
excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
这句话的意思是,排除各种已经的不是内存泄漏的情况。其实为什么要在第一步中指出,这是一个 Builder呢,我们常见的 builder 代码里,有各种各样的赋值,但最关键的代码往往只是它的 build 方法,我们别被这种链式调用弄晕了,这些不重要,别死抠细节不放,大概明白意思就行,接下来我们看最重要的方法:
public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
//调用build方法,构建 RefWatcher
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
if (enableDisplayLeakActivity) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
if (watchActivities) {
//监听activity
ActivityRefWatcher.install(context, refWatcher);
}
//监听 fragment
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
build 方法返回 RefWatcher ,比较简单,其实就是将之前链式调用赋值的各个对象,赋值给 RefWatcher
public final RefWatcher build() {
if (isDisabled()) {
return RefWatcher.DISABLED;
}
if (heapDumpBuilder.excludedRefs == null) {
heapDumpBuilder.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();
}
if (heapDumpBuilder.reachabilityInspectorClasses == null) {
heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
}
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
heapDumpBuilder);
}
最最关键的还得是 install 方法,本文中我们只分析 activity 的逻辑,fragment 暂不分析。
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);
}
install 方法中只有三行,第一步,获取 Application 对象,第二步,生成一个 ActivityRefWatcher 对象,第三步,看方法名,貌似是注册了一个监听,一个activity生命周期的监听。看看 lifecycleCallbacks 这个回调对象:
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
关键代码终于出现,当activity 调用 onDestroyed 方法时,会调用lifecycleCallbacks 中的方法,从而可以拿到 activity 的引用。结合之前 Reference 的分析,要监听对象是否被回收,首先得拿到它的引用,现在 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);
}
通过拿到的 activity 引用,构造 KeyedWeakReference 对象,其实它继承自 WeakReference ,它是一个弱引用。构建弱引用的同时,在构造函数中添加 ReferenceQueue,当 activity 被回收时,KeyedWeakReference 对象会被添加到ReferenceQueue当中。如果出现内存泄漏,则 ReferenceQueue 找不到对应的 KeyedWeakReference 对象,那么就可以判断发生内存泄漏了。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
ensureGoneAsync方法中,通过 watchExecutor 执行一个 run 方法,watchExecutor 只会在主线程中执行它,继续查看 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;
}
//gone方法即是查看到 activity 已经被回收,则返回 DONE,表示内存无漏泄
if (gone(reference)) {
return DONE;
}
//调用 gc
gcTrigger.runGc();
removeWeaklyReachableReferences();
//调用gc之后,再次检查 ,如果 activity 还没被回收,则是有内存泄漏了
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();
// 分析 heap 的prof文件,找出内存泄漏点
heapdumpListener.analyze(heapDump);
}
return DONE;
}
如果 activity 已经被回收,那么 ReferenceQueue 中将添加指向它的 Reference,这是一条大原则,一定要记住。在 watch 方法时,为每个 activity构造对应的Reference时,还添加了一个key,并把key添加到一个set当中。
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 中得到的Reference,拿到 Reference,同时删除 set 中对应的key。所以,set中不包含某个 key,则说明对应的 activity已经被回收,反之则是没有回收。
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
gone方法正好验证这点,如果gone返回为true,那么整个过程也结束了。如果gone返回为false,则表明可能有内存泄漏,所以执行一次gc之后 ,再次调用gone方法,查看是否有无泄漏。如果还有泄漏,则分析生成的prof文件,找出关键的泄漏路径。关于如何分析 prof 文件,此处不展开了,其实 LeakCanary 也是借用其它的开源库来分析的。
最后总结下整个过程:
在一个Activity执行完onDestroy()之后,将它放入WeakReference中,然后将这个WeakReference类型的Activity对象与ReferenceQueque关联。这时再从ReferenceQueque中查看是否有没有该对象,如果没有,执行gc,再次查看,还是没有的话则判断发生内存泄露了。最后用HAHA这个开源库去分析dump之后的heap内存。
网友评论