美文网首页
LeakCanary源码分析

LeakCanary源码分析

作者: MadnessXiong | 来源:发表于2020-03-18 08:55 被阅读0次

    LeakCanary原理概述: 在Java中,一个对象不再被任何GC roots引用,那么虚拟机在gc时就会被回收。 如果一个对象已经完成了它的任务,但是还是被其他引用所引用,对象所占用的内存得不到释放,那么就会造成内存泄露。

    Java中提供了WeakReference(弱引用),当一个对象只有被弱引用引用时,那么在gc发生时就会被回收(),弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

    LeakCanary的原理就是这样,判断一个对象是否内存泄漏,先把这个对象添加到一个WeakReference集合中,然后调用gc,把gc后的对象放入ReferenceQueue中,通过ReferenceQueue中是否存在这个引用,来判断对象是否被回收,如果没被回收,则代表发生了内存泄漏。

    LeakCanary源码分析:

    先看代码:

          if (LeakCanary.isInAnalyzerProcess(this)){
                return;
            }
            LeakCanary.install(this);
            //备注:LeakCanary2.0以后已经不需要自己去设置以上2行代码了
    

    LeakCanary的使用只需要在Application里执行以上代码,那么看一下install()的内部实现:

      public static @NonNull RefWatcher install(@NonNull Application application) {
                    //第一行主要是添加内存泄漏时,顶部的通知栏逻辑,不重点关注
        return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
                //第二行主要是过滤一些无法处理的内存泄漏,比如android本身的内存泄漏,已知的第三方库的内存泄漏等
            .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
            //创建并执行LeakCanary
            .buildAndInstall();
      }
    

    那么重点看一下第三行:

     public @NonNull RefWatcher buildAndInstall() {
        //创建一个watcher,不必重点关注这里的实现
        RefWatcher refWatcher = build();
            //是否监听Activity
          if (watchActivities) {
            ActivityRefWatcher.install(context, refWatcher);
          }
         //是否监听Activity
          if (watchFragments) {
            FragmentRefWatcher.Helper.install(context, refWatcher);
          }
        }
        LeakCanaryInternals.installedRefWatcher = refWatcher;
        return refWatcher;
      }
    

    这里以如何监听Activity的内存泄漏为例,看一下ActivityRefWatcher的install():

     public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
        Application application = (Application) context.getApplicationContext();
        //对watcher进行包装
        ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
        //注册ActivityLifecycleCallbacks
        application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
      }
    

    可以看到这里先是把RefWatcher包装成了一个ActivityRefWatcher,然后注册了ActivityLifecycleCallbacks,那么当activity的onDestory()执行时,也就是activity销毁时,就可以通过传入的lifecycleCallbacks收到它的回调。那么看一下代码:

    ActivityLifecycleCallbacks一经注册,就会自动监听整个APP 中所有Activity 的生命周期方法的执行(会在对应的父类Activity的方法被执行之后自动触发)。

      private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
          new ActivityLifecycleCallbacksAdapter() {
            @Override public void onActivityDestroyed(Activity activity) {
              //watch当前activity对象
              refWatcher.watch(activity);
            }
          };
    

    这里调用了RefWatcher的watch(),并把需要watch的对象activity传了进来:

     public void watch(Object watchedReference) {
        watch(watchedReference, "");
      }
      public void watch(Object watchedReference, String referenceName) {
        if (this == DISABLED) {
          return;
        }
        //生成一个唯一ID
        String key = UUID.randomUUID().toString();
        //将唯一ID添加到一个Set集合中,只要代表对象的key还存在这个集合中,那么代表对象还没被回收
        retainedKeys.add(key);
        //KeyedWeakReference是一个弱引用,这里把key和watchedReference以及queue(ReferenceQueue)包装成一个弱引用对象
        final KeyedWeakReference reference =
            new KeyedWeakReference(watchedReference, key, referenceName, queue);
            //核心逻辑
        ensureGoneAsync(watchStartNanoTime, reference);
      }
    

    可以看到,首先记录了开始watch的时间,然后生成了一个唯一ID用作key,用来和被watch的对象绑定。然后把被watch对象包装成一个弱引用对象。再看ensureGoneAsync():

     private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
        //这里的watchExecutor实际是AndroidWatchExecutor
        watchExecutor.execute(new Retryable() {
          @Override public Retryable.Result run() {
            //这里执行在子线程中
            return ensureGone(reference, watchStartNanoTime);
          }
        });
      }
    

    可以看到,实际是执行了AndroidWatchExecutor的execute():

        @Override public void execute(@NonNull Retryable retryable) {
        if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
          waitForIdle(retryable, 0);
        } else {
          postWaitForIdle(retryable, 0);
        }
      }
      private void waitForIdle(final Retryable retryable, final int failedAttempts) {
        // This needs to be called from the main thread.
        //通过addIdleHandler的方式,让主线程在空闲时才去执行postToBackgroundWithDelay()
        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
          @Override public boolean queueIdle() {
            postToBackgroundWithDelay(retryable, failedAttempts);
            return false;
          }
        });
      }
    
      private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
        long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
        long delayMillis = initialDelayMillis * exponentialBackoffFactor;
        //backgroundHandler绑定的是一个HandlerThread的looper,所以这里将线程切换到了子线程
        backgroundHandler.postDelayed(new Runnable() {
          @Override public void run() {
            Retryable.Result result = retryable.run();
            if (result == RETRY) {
              postWaitForIdle(retryable, failedAttempts + 1);
            }
          }
        }, delayMillis);
      }
    

    可以看到在execute()中首先首先在主线程空闲时才去执行postToBackgroundWithDelay(),这是因为主线程空闲了,那么被watch的对象,也就是activity才真正被释放了。

    最后再把线程切到子线程执行run(),最终会执行刚才的ensureGone():

    activity的onDestory执行时,不会从activity集合中删除,执行完后才会再去执行删除的代码。所以为了保证activity不再被引用,必须等到主线程空闲时,也就是activity被删除引用的代码也被执行了,而不是onDestory执行完。详见ActivityThread类的performDestroyActivity()。

      Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
            //将已经gc掉的对象代表的key移除
        removeWeaklyReachableReferences();
        
        if (gone(reference)) {
            //如果对象已被回收,那么代表不存在内存泄漏,直接返回
          return DONE;
        }
        //主动进行gc
        gcTrigger.runGc();
        ////将已经gc掉的对象代表的key移除
        removeWeaklyReachableReferences();
        //判断被观察到对象是否被回收
        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);
    
          HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
              .referenceName(reference.name)
              .watchDurationMs(watchDurationMs)
              .gcDurationMs(gcDurationMs)
              .heapDumpDurationMs(heapDumpDurationMs)
              .build();
    
          heapdumpListener.analyze(heapDump);
        }
        return DONE;
      }
        //移除已经被gc掉的对象的key
      private void removeWeaklyReachableReferences() {
            //如果queue中存在这个对象,那么代表已经被回收,那么从保存的set集合retainedKeys中移除代表它的key
        KeyedWeakReference ref;
        while ((ref = (KeyedWeakReference) queue.poll()) != null) {
          retainedKeys.remove(ref.key);
        }
      }
        //判断是代表集合的key是否还存在,代表对象是否已经被回收,true代表已回收,false代表未回收
      private boolean gone(KeyedWeakReference reference) {
        return !retainedKeys.contains(reference.key);
      }
    

    可以看到,到这里就可以判断出是否发生了内存泄漏。

    总结

    • 首先注册了ActivityLifecycleCallbacks,那么当activity被销毁时,也就是它的onDestory()被执行时,LeakCanary可以收到回调
    • 收到回调后调用watch(),对当前activity进行观察,并生成一个唯一ID-key,保存到Set集合retainedKeys中。retainedKeys中存储了被观察且未被回收的对象的key。
    • 将key和被观察到对象包装成一个KeyedWeakReference(弱引用)对象,并传入一个ReferenceQueue对象queue,那么当对象被回收后,就会放入这个queue中。
    • 然后执行了AndroidWatchExecutor的execute(),这个方法中主要做了2件事,1是确保在主线程空闲时开始执行任务,因为当Activity执行onDestory时,它还没有被移除,需要等它执行完后才会被移除。那么主线程空闲代表已经执行完了方法。2是将线程切换到了自线程。
    • 线程切换到子线程后执行了ensureGone(),在这个方法中主动调用gc,gc后所有该被回收到对象已被回收,这时调用removeWeaklyReachableReferences(),这个方法中会从之前的ReferenceQueue对象queue中取对象,前面说过如果对象被回收,就会放入这个queue中,那么如果从这个queue中取出了对象,那么就把这些对象对应的key从retainedKeys中移除。
    • 再调用gone()方法,判断当前activity对应的key是否还在retainedKeys中,如果不在,代表已经被回收。如果还在,代表发生了内存泄漏。
    • 如果发生了内存泄漏,那么会进一步分析堆文件,找出泄漏的对象,发送通知等操作。(这里不是重点,不再细致展开)

    相关文章

      网友评论

          本文标题:LeakCanary源码分析

          本文链接:https://www.haomeiwen.com/subject/seofyhtx.html