LeakCanary是Android中用于排查Activity、Fragment内存泄漏的工具。
本文基于LeakCanary 1.6.1源码分析。
1 | LeakCanary.install(this); |
- DisplayLeakService和DisplayLeakActivity用于确定分析Heap内存信息并将泄漏信息显示。
- AndroidExcludedRefs记录了Android源码中内存泄漏的地方,如果项目中出现也会提示,并添加[Excluded]标志。
- 通过ActivityRefWatcher监控Activity内存泄漏。
- 通过FragmentRefWatcher监控Fragment内存泄漏。
接下来看看ActivityRefWatcher如何分析Acitivty内存泄漏。
1 | private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() { |
ActivityRefWatcher利用registerActivityLifecycleCallbacks监控Activity生命周期,注册了一个ActivityLifecycleCallbacks。
ActivityLifecycleCallbacks在Activity的onDestroy时使用RefWatcher进行watch。
接下来进入RefWatcher看看watch方法。
1 | public void watch(Object watchedReference, String referenceName) { |
retainedKeys是一个Set,通过UUID生成一个key与Activity对应。retainedKeys存放的是使用中的引用(即Activity)key值。
ReferenceQueue,用来监听GC后的回收情况,在Acitivity.onDestroy回收成功的Activity引用会存放进这个队列。反过来说,即ReferenceQueue存放的都是释放成功的Activity引用,从ReferenceQueue取出的值都表示该Activity已被正确回收。
gc后弱引用被回收,retainedKeys中还存在有引用,则代表可能发生了内存泄漏。
接下来看看ensureGone方法,这也是LeakCanary的核心原理。
1 | Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) { |
removeWeaklyReachableReferences则用于判断Activity是否进行了正确的内存回收。
1 | private void removeWeaklyReachableReferences() { |
这里为何要进行两次gc,为何不是一次,也不是三四次?
看一下官方对弱引用的定义:
假设垃圾收集器在某个时间点决定一个对象是弱可达的(weakly reachable)(也就是说当前指向它的全都是弱引用),这时垃圾收集器会清除所有指向该对象的弱引用,然后把这个弱可达对象标记为可终结(finalizable)的,这样它随后就会被回收。与此同时或稍后,垃圾收集器会把那些刚清除的弱引用放入创建弱引用对象时所指定的引用队列(Reference Queue)中。
可能存在被观察的引用将要变得弱可达,但是还未入队引用队列,这个时候可能会错误判断这个对象未被回收,所以这时候应该再主动调用一次 GC,可能可以避免一次heap dump。频繁的heap dump会卡住应用。
至此,LeakCanary的核心原理已经分析完毕。
后续利用 heapDumper 把内存情况 dump 成文件,并调用 heapdumpListener 进行内存分析,进一步确认是否发生内存泄漏。
gc是会耗费时间的,影响主线程性能,那么LeakCanary是怎么做处理的。
Android的Handler、Looper机制中,主线程Looper在循环从MessageQueue中取消息时,会执行MQ的next方法,Android在MQ里预留了mIdleHandlers,用于处理空闲任务。即主线程没有消息处理时。
LeakCanary在RefWatch的ensureGoneAsync方法,会利用到mIdleHandlers。
1 | private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) { |
AndroidWatchExecutor的execute方法如下。
1 | public void execute(Retryable retryable) { |
两者最终都是通过waitForIdle往MainHandler添加一个IdleHandler。
1 | private void waitForIdle(final Retryable retryable, final int failedAttempts) { |
最后通过postToBackgroundWithDelay方法实现。
1 | private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) { |
最近又查看了LeakCanary 2.0版本的代码,使用了kotlin进行改写,但是核心流程是一样的。
LeakCanary2不需要开发者在Application类手动调用install。LeakCanary2通过Manifest注册一个Provider,实现Provider的方法并在onCreate获取Application的Context,由于Provider的onCreate生命周期在Application的onCreate之前,于是无需在Application的onCreate进行注册。
最后总结一下:
- 在Application中LeakCanary.install时,通过AndroidRefWatcherBuilder创建RefWatch用于监控内存泄漏情况。
- ActivityRefWatcher利用registerActivityLifecycleCallbacks监控Activity生命周期,在Activity的onDestroy时使用RefWatcher进行watch。
- LeakCanary利用MessageQueue中的IdleHandler,在UI空闲时间处理gc和内存泄漏监控,防止影响UI性能。
- 检查内存泄漏的核心是利用WeakReference包装Activity,使用一个ReferenceQueue来记录该 WeakReference 指向的对象是否已被回收。当系统回收或者手动触发gc回收Acitivty,成功回收的Activity的WeakReference会加入ReferenceQueue,ReferenceQueue取出的即使正确被回收的Activity,retainedKeys留下记录未被回收的Activity的key。
- 通过dump出Heap文件,然后通过分析Heap文件的内存情况,最终通过DisplayLeakService和DisplayLeakActivity将内存泄漏信息通知和显示。
参考文章
http://wingjay.com/2017/05/14/dig_into_leakcanary/
Java内存问题 及 LeakCanary 原理分析 - 掘金
https://juejin.im/post/5d1225546fb9a07ecd3d6b71