为了使文章尽量通俗易懂。在探究
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发生时为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,同时创建一个该Activityd的弱引用对象KeyedWeakReference,并将UUID写入KeyedWeakReference实例中,同时KeyedWeakReference与引用队列queue进行关联,这样当Activity对象正常回收时,该弱引用对象将进入队列当中。
循环遍历获取queue队列中的KeyedWeakReference对象ref,将ref中的UUID取出,在retainedKeys中移除该UUID。如果遍历完成后retainedKeys中仍然存在该弱引用的UUID,则说明该Activity对象在onDestory调用后没有被正常回收。此时通过GcTrigger手动发起一次GC,再等待100ms,然后再次判断Activity是否被正常回收,如果没有被回收,则开始内存泄漏和展示信息的工作。