「文章在2019年已发布在我的CSDN博客《LeakCanary原理从0到1》, 迁移至此,复习一下」
为了使文章尽量通俗易懂。在探究LeakCanary
之前,有必要补充些Java
引用的知识。
引用分类
强引用
强引用是使用最普遍的引用。一个对象具有强引用,则在
GC
发生时,该对象将不会回收。当Jvm虚拟机内存空间不足时,虚拟机会抛出OutOfMemoryError
错误,不会回收具有强引用的对象来解决内存不足的问题。
软引用
当一个对象只有软引用,若虚拟机内存空间足够,垃圾回收器就不会回收该对象; 若内存空间不足,下次
GC
时这些只有软引用对象将被回收。若垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。在创建软引用实例时,可以传入一个引用队列(
ReferenceQueue
)将该软引用与引用队列关联,这样当软引用所引用的对象被垃圾回收器回收前,Jvm虚拟机会把这个软引用加入到与之关联的引用队列中。
弱引用
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在
GC
发生时,若一个对象只有弱引用,不管虚拟机内存空间是否足够,都会回收它的内存。在创建弱引用实例时,可以传入一个引用队列(
ReferenceQueue
)将该软引用与引用队列关联,这样当软引用所引用的对象被垃圾回收器回收前,Jvm虚拟机就会把这个软引用加入到与之关联的引用队列中。
虚引用
虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
在创建虚引用实例时,可以传入一个引用队列(
ReferenceQueue
)将该软引用与引用队列关联,这样当软引用所引用的对象被垃圾回收器回收前,Jvm虚拟机就会把这个软引用加入到与之关联的引用队列中。
软引用、弱引用、虚引用的构造方法均可以传入一个ReferenceQueue
与之关联。在引用所指的对象被回收后,引用(reference
)本身将会被加入到ReferenceQueue
之中,此时引用所引用的对象reference.get()
已被回收 (reference
此时不为null
,reference.get()
此时为null)。
所以,在一个非强引用所引用的对象回收时,如果引用reference
没有被加入到被关联的ReferenceQueue
中,则表示还有引用所引用的对象还没有被回收。如果判断一个对象的非强引用本该出现在ReferenceQueue
中,实际上却没有出现,则表示该对象发送内存泄漏。
LeakCanary
理论依据
当一个Activity
的onDestory
方法被执行后,说明该Activity
的生命周期已经走完,在下次GC
发生时,该Activity
对象应将被回收。
- 通过上面对引用的学习,可以考虑在
onDestory
发生时创建一个弱引用指R向Activity
,并关联一个RefrenceQuence
,当Activity
被正常回收,弱引用实例本身应该出现在该RefrenceQuence
中,否则便可以判断该Activity
存在内存泄漏。 - 通过
Application.registerActivityLifecycleCallbacks()
方法可以注册Activity
生命周期的监听,每当一个Activity
调用onDestroy
进行页面销毁时,去获取到这个Activity
的弱引用并关联一个ReferenceQuence
,通过检测ReferenceQuence
中是否存在该弱引用判断这个Activity
对象是否正常回收。 - 当
onDestory
被调用后,初步观察到Activity
未被GC
正常回收时,手动触发一次GC
,由于手动发起GC
请求后并不会立即执行垃圾回收,所以需要在一定时延后再二次确认Activity
是否已经回收,如果再次判断Activity
对象未被回收,则表示Activity
存在内存泄漏。
源码解析
- 在导入依赖后使用如下方法便可以使用
LeakCanary
进行Activity
内存泄漏分析:
//:MyApp.java public class MyApp 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 LeakCanary.install(this); } }
LeakCanary 2.0 的初始化放在了自带的ContentProvider中:
ContentProvider的
onCreate
的调用时机介于Application的attachBaseContext
和onCreate
之间,LeakCanary 2.0将LeakCanary的初始化放在了自带的ContentProvider的onCreate
函数中,将multiprocess
设为false
可以保证ContentProvider只初始化一次,LeakCanary也只初始化一次
- 进入
LeakCanary#install(Application application)
//:LeakCanary.java public final class LeakCanary { /** * Creates a {@link RefWatcher} that works out of the box, and starts watching activity * references (on ICS+). */ public static RefWatcher install(Application application) { return refWatcher(application) .listenerServiceClass(DisplayLeakService.class)//内存泄漏后用于显示的线上泄漏信息 .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())//白名单 .buildAndInstall(); } /** Builder to create a customized {@link RefWatcher} with appropriate Android defaults. */ public static AndroidRefWatcherBuilder refWatcher(Context context) { return new AndroidRefWatcherBuilder(context); } }
- 在
install(Application application)
内部,通过refWatcher
创建了一个AndroidRefWatcherBuilder
对象。- 由命名可以看出这是个
builder
模式,在阅读这种设计模式的代码时,有一些小技巧:在build()方法调用前基本进行的就是一些变量赋值的操作,只需要留意build()返回的对象即可,所以这里重点关注buildAndInstall()
的调用。
- 深入
AndroidRefWatcherBuilder#buildAndInstall()
AndroidRefWatcherBuilder
继承RefWatcherBuilder
,在buildAndInstall()
中创建一个RefWatcher
(引用勘探者)实例,并使用静态方法ActivityRefWatcher#installOnIcsPlus()
将Application.ActivityLifecycleCallbacks
对象注册到Application
对象当中,每当有Activity
调用onActivityDestroyed
方法时,程序将回调RefWatcher
的watch(Activity activity)
方法。- 内存泄漏的判断、分析以及泄漏信息的显示均在
RefWatcher#watch(Activity activity)
完成。
//:AndroidRefWatcherBuilder.java /** * Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+). */ public RefWatcher buildAndInstall() { RefWatcher refWatcher = build();//构建引用勘探者 ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher); } return refWatcher; } //: ActivityRefWatcher.java @TargetApi(ICE_CREAM_SANDWICH) public final class ActivityRefWatcher { public static void installOnIcsPlus(Application application, RefWatcher refWatcher) { if (SDK_INT < ICE_CREAM_SANDWICH) { // If you need to support Android < ICS, override onDestroy() in your base activity. return; } ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher); //完成Activity生命周期的注册工作 activityRefWatcher.watchActivities(); } 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) { ActivityRefWatcher.this.onActivityDestroyed(activity); } }; private final Application application; private final RefWatcher refWatcher; /** * Constructs an {@link ActivityRefWatcher} that will make sure the activities are not leaking * after they have been destroyed. */ public ActivityRefWatcher(Application application, final RefWatcher refWatcher) { this.application = checkNotNull(application, "application"); this.refWatcher = checkNotNull(refWatcher, "refWatcher"); } void onActivityDestroyed(Activity activity) { refWatcher.watch(activity); } public void watchActivities() { // Make sure you don't get installed twice. stopWatchingActivities(); application.registerActivityLifecycleCallbacks(lifecycleCallbacks); } public void stopWatchingActivities() { application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks); } }
RefWatcher#watch(Activity activity)
分析
- 上文讲到内存泄漏的判断、分析以及泄漏信息的显示均在
RefWatcher#watch(Activity activity)
完成,因此不得不去此方法中一探究竟。 在正式开始源码之前有必要交代一下:
KeyedWeakReference
是WeakReference
的一个子类,它的作用是持有一个Activity
的弱引用并为每一个Activity
实例绑定一个全局唯一的Key
(具体采用的方法是调用UUID.randomUUID().toString();
)ReferenceQueue
是一个队列
public final class RefWatcher { public static final RefWatcher DISABLED = new RefWatcherBuilder<>().build(); private final WatchExecutor watchExecutor; private final DebuggerControl debuggerControl; //GC触发器,手动发起GC private final GcTrigger gcTrigger; private final HeapDumper heapDumper; //与各个Activity关联的唯一UUID容器 private final Set<String> retainedKeys; //引用队列,当Activity被正常回收时,该Activity的弱引用将被放入其中 private final ReferenceQueue<Object> queue; private final HeapDump.Listener heapdumpListener; //白名单,白名单内的对象可以有效持有Activity,避开内存检测 private final ExcludedRefs excludedRefs; RefWatcher(WatchExecutor watchExecutor, DebuggerControl debuggerControl, GcTrigger gcTrigger, HeapDumper heapDumper, HeapDump.Listener heapdumpListener, ExcludedRefs excludedRefs) { this.watchExecutor = checkNotNull(watchExecutor, "watchExecutor"); this.debuggerControl = checkNotNull(debuggerControl, "debuggerControl"); this.gcTrigger = checkNotNull(gcTrigger, "gcTrigger"); this.heapDumper = checkNotNull(heapDumper, "heapDumper"); this.heapdumpListener = checkNotNull(heapdumpListener, "heapdumpListener"); this.excludedRefs = checkNotNull(excludedRefs, "excludedRefs"); retainedKeys = new CopyOnWriteArraySet<>(); queue = new ReferenceQueue<>(); } /** * Identical to {@link #watch(Object, String)} with an empty string reference name. * * @see #watch(Object, String) */ public void watch(Object watchedReference) { watch(watchedReference, ""); } /** * Watches the provided references and checks if it can be GCed. This method is non blocking, * the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed * with. * * @param referenceName An logical identifier for the watched object. */ 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); } private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) { watchExecutor.execute(new Retryable() { @Override public Retryable.Result run() { return ensureGone(reference, watchStartNanoTime); } }); } Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime(); long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); //在retainedKeys中移除进入quene中的KeyedWeakReference所对应的UUID removeWeaklyReachableReferences(); if (debuggerControl.isDebuggerAttached()) { // The debugger can create false leaks. return RETRY; } //若retainedKeys中没有对应的UUID if (gone(reference)) { return DONE; } //若retainedKeys中存在对应的UUID,发起手动GC,并睡眠100ms gcTrigger.runGc(); //再次在retainedKeys中移除进入quene中的KeyedWeakReference所对应的UUID removeWeaklyReachableReferences(); //若retainedKeys中仍然存在对应的UUID,则开始内存泄漏和展示信息的工作 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); //内存泄漏后的信息分享,最终在DisplayLeakService#onHeapAnalyzed中通过Notification显示处理 heapdumpListener.analyze( new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs)); } return DONE; } //弱引用对应的UUID是否已被移除 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. KeyedWeakReference ref; //从被回收的对象的弱引用队列中取出一个弱引用对象,并在retainedKeys中移除对应的UUID while ((ref = (KeyedWeakReference) queue.poll()) != null) { retainedKeys.remove(ref.key); } } }
当
Activity
的onDestory
方法被调用后,LeakCanary
将在RefWatcher
的retainedKeys
加入一条全局唯一的UUID
,同时创建一个该Activity
d的弱引用对象KeyedWeakReference
,并将UUID
写入KeyedWeakReference
实例中,同时KeyedWeakReference
与引用队列queue
进行关联,这样当Activity
对象正常回收时,该弱引用对象将进入队列当中。 循环遍历获取queue
队列中的KeyedWeakReference
对象ref
,将ref
中的UUID
取出,在retainedKeys
中移除该UUID
。如果遍历完成后retainedKeys
中仍然存在该弱引用的UUID
,则说明该Activity
对象在onDestory
调用后没有被正常回收。此时通过GcTrigger
手动发起一次GC
,再等待100ms,然后再次判断Activity
是否被正常回收,如果没有被回收,则开始内存泄漏和展示信息的工作。
拓展
RefWatch
可以用作监控任意的普通对象,如下:
在demoInstacne
需要被回收的位置监控其是否被正常回收。
LeakCanaryInternals.installedRefWatcher.watch(demoInstance);