LeakCanary源码分析
概述
LeakCanary是用来检测 Java 和 Android 内存泄露的工具。
LeakCanary的原理非常简单。正常情况下一个Activity在onDestroy之后就要销毁,LeakCanary做的就是在一个Activity onDestroy之后将它放在一个WeakReference中,然后将这个WeakReference关联到一个ReferenceQueue。这个ReferenceQueue的作用是,当Activity被回收的时候,系统会将其Activity对应的WeakReference对象加入到ReferenceQueue。
然后我们查看ReferenceQueue是否存在WeakReference对象,如果存在说明Activity已经被回收。如果不存在,执行GC操作,再查看是否被回收。如果不存在则证明该Activity泄漏了,之后Dump出heap信息,并用haha这个开源库去分析泄漏路径。
LeakCanary的使用很简单,如下:
public class ExampleApplication extends Application { @Override public void onCreate() { super.onCreate(); 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); // Normal app init code... }}
源码分析
install
走进install():
public static RefWatcher install(Application application) { // 添加了监听器,排除了一些不需要观察的类并且完成了创建 return refWatcher(application).listenerServiceClass(DisplayLeakService.class) .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) .buildAndInstall(); } /** * Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+). */ public RefWatcher buildAndInstall() { RefWatcher refWatcher = build(); if (refWatcher != DISABLED) { LeakCanary.enableDisplayLeakActivity(context); // 将观察者注入进了Application中 ActivityRefWatcher.install((Application) context, refWatcher); } return refWatcher; } public static void install(Application application, RefWatcher refWatcher) { new ActivityRefWatcher(application, refWatcher).watchActivities(); }
以上代码所做的主要内容就是创建了一个Activity内存泄露的监听器,注入到了Application中。
watchActivities
然后进入watchActivities():
public void watchActivities() { // Make sure you don't get installed twice. stopWatchingActivities(); // 注册了一个Activity生命周期的监听器 application.registerActivityLifecycleCallbacks(lifecycleCallbacks); } 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) { // 当Activity销毁的时候回调refWatcher的watch()函数 ActivityRefWatcher.this.onActivityDestroyed(activity); } }; void onActivityDestroyed(Activity activity) { refWatcher.watch(activity); }
watch
watch()最后调用的重载函数:
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(); // 加入到一个集合中 retainedKeys.add(key); // 将Activity包裹成一个弱引用对象 final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue); // 检测内存泄露,确保Activity真的被回收 ensureGoneAsync(watchStartNanoTime, reference); } private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) { watchExecutor.execute(new Retryable() { @Override public Retryable.Result run() { // 这个方法会在Android主线程空闲的时候执行 return ensureGone(reference, watchStartNanoTime); } });
ensureGone
进入ensureGone():
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) { // 计算从开始观察到gc所用的时间 long gcStartNanoTime = System.nanoTime(); long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); // 清除已经进入ReferenceQueue的弱引用 // 把已被回收的对象的key从retainedKeys移除,剩下的key都是未被回收的对象 removeWeaklyReachableReferences(); if (debuggerControl.isDebuggerAttached()) { // The debugger can create false leaks. return RETRY; } if (gone(reference)) { // 如果当前的对象已经弱可达,说明不会造成内存泄漏 return DONE; } // 否则手动调用gc,以防止系统并没有回收,误判 gcTrigger.runGc(); // 清除已经进入ReferenceQueue的弱引用 removeWeaklyReachableReferences(); if (!gone(reference)) { // 内存泄露 long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); // dump出来heap File heapDumpFile = heapDumper.dumpHeap(); if (heapDumpFile == RETRY_LATER) { // Could not dump the heap. return RETRY; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); // 去分析 heapdumpListener.analyze( new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs)); } return DONE; } private boolean gone(KeyedWeakReference reference) { return !retainedKeys.contains(reference.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. // 在实际垃圾回收之前弱引用就会被加入ReferenceQueue队列 KeyedWeakReference ref; while ((ref = (KeyedWeakReference) queue.poll()) != null) { retainedKeys.remove(ref.key); } }
其中gcTrigger.runGc();
如何保证肯定gc呢:
public interface GcTrigger { 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. // 触发系统gc操作 Runtime.getRuntime().gc(); // 通过强制限制100毫秒的时间给gc enqueueReferences(); // 强制调用已经失去引用的对象的finalize方法 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(); } } }; void runGc();}
大概意思是system.gc()
并不会每次都立即执行,这里从AOSP中拷贝一段GC的代码,从而保证能够进行垃圾清理工作。
后续的如何导出文件,分析,提示内存泄露并不是重点,这里省略了。
疑问
判断引用是否被回收的时候,为什么不直接使用reference.get()
,是不是因为这个时候reference.get()
的结果有可能不是null,但是已经加入了ReferenceQueue,证明马上就要被回收了。使用reference.get()
,更加准确。
参考:
1.深入理解 Android 之 LeakCanary 源码解析
2.LeakCanary源码分析
3.译文:理解Java中的弱引用