LeakCanary原理从0到1

6,736 阅读9分钟

「文章在2019年已发布在我的CSDN博客《LeakCanary原理从0到1》, 迁移至此,复习一下」

为了使文章尽量通俗易懂。在探究LeakCanary之前,有必要补充些Java引用的知识。

引用分类

强引用

强引用是使用最普遍的引用。一个对象具有强引用,则在GC发生时,该对象将不会回收。当Jvm虚拟机内存空间不足时,虚拟机会抛出OutOfMemoryError错误,不会回收具有强引用的对象来解决内存不足的问题。

软引用

当一个对象只有软引用,若虚拟机内存空间足够,垃圾回收器就不会回收该对象; 若内存空间不足,下次GC时这些只有软引用对象将被回收。若垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。

在创建软引用实例时,可以传入一个引用队列(ReferenceQueue)将该软引用与引用队列关联,这样当软引用所引用的对象被垃圾回收器回收前,Jvm虚拟机会把这个软引用加入到与之关联的引用队列中。

弱引用

弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在GC发生时,若一个对象只有弱引用,不管虚拟机内存空间是否足够,都会回收它的内存。

在创建弱引用实例时,可以传入一个引用队列(ReferenceQueue)将该软引用与引用队列关联,这样当软引用所引用的对象被垃圾回收器回收前,Jvm虚拟机就会把这个软引用加入到与之关联的引用队列中。

虚引用

虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

在创建虚引用实例时,可以传入一个引用队列(ReferenceQueue)将该软引用与引用队列关联,这样当软引用所引用的对象被垃圾回收器回收前,Jvm虚拟机就会把这个软引用加入到与之关联的引用队列中。

软引用弱引用虚引用的构造方法均可以传入一个ReferenceQueue与之关联。在引用所指的对象被回收后,引用(reference)本身将会被加入到ReferenceQueue之中,此时引用所引用的对象reference.get()已被回收 (reference此时不为nullreference.get()此时为null)。

所以,在一个非强引用所引用的对象回收时,如果引用reference没有被加入到被关联的ReferenceQueue中,则表示还有引用所引用的对象还没有被回收。如果判断一个对象的非强引用本该出现在ReferenceQueue中,实际上却没有出现,则表示该对象发送内存泄漏。

LeakCanary

理论依据

当一个ActivityonDestory方法被执行后,说明该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存在内存泄漏。

源码解析

  1. 在导入依赖后使用如下方法便可以使用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的attachBaseContextonCreate之间,LeakCanary 2.0将LeakCanary的初始化放在了自带的ContentProvider的onCreate函数中,将multiprocess设为false可以保证ContentProvider只初始化一次,LeakCanary也只初始化一次

  1. 进入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()的调用。
  1. 深入AndroidRefWatcherBuilder#buildAndInstall()
  • AndroidRefWatcherBuilder继承RefWatcherBuilder,在buildAndInstall()中创建一个RefWatcher(引用勘探者)实例,并使用静态方法ActivityRefWatcher#installOnIcsPlus()Application.ActivityLifecycleCallbacks对象注册到Application对象当中,每当有Activity调用onActivityDestroyed方法时,程序将回调RefWatcherwatch(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);
 		}
 }
  1. RefWatcher#watch(Activity activity)分析
  • 上文讲到内存泄漏的判断、分析以及泄漏信息的显示均在RefWatcher#watch(Activity activity)完成,因此不得不去此方法中一探究竟。 在正式开始源码之前有必要交代一下:
  1. KeyedWeakReferenceWeakReference的一个子类,它的作用是持有一个Activity的弱引用并为每一个Activity实例绑定一个全局唯一的Key(具体采用的方法是调用UUID.randomUUID().toString();)
  2. 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);
   }
 }
}

ActivityonDestory方法被调用后,LeakCanary将在RefWatcherretainedKeys加入一条全局唯一的UUID,同时创建一个该Activityd的弱引用对象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);