美文网首页
LeakCanary详解

LeakCanary详解

作者: 沉迷学习_日渐发福 | 来源:发表于2018-11-19 22:46 被阅读0次

    LeakCanary简介

    leakCanary是square公司推出的一个用于检测内存泄漏的工具,在一个activity完全ondestroy方法执行时,我们都会希望它的内存空间能够完全被回收。但是实际上并非是这样的,往往可能会出现内存泄漏,比如说这个activity被其他没有回收的类所持有引用。那么就会造成ondestory方法执行完之后,这个activity没有被回收,造成内存泄漏。所以,为了让我们在开发测试时更容易的发现内存泄漏的情况,square公司推出了这个开源库。

    LeakCanary流程

    LeakCanary的使用

    对于一个app引入leakCanary这个库是非常的简单的,只需要在gradle文件中引入下面几个依赖,这几个依赖会针对不同的编译版本引入不同的包:

    dependencies {
       debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
       releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
       testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
     }
    

    然后在代码中,我们只需要在我们项目中的application类去调用install方法建立LeakCanary监测就行了。

      if (LeakCanary.isInAnalyzerProcess(this)) {
            //这个是在分析heap的进程中,监测进程不能和分析进程在同一个。
        return;
        }
        LeakCanary.install(this);
    

    LeakCanary的简单实现流程

    在我们在Application类中调用了LeakCanary的install方法之后,就开始整个activity的监测流程。

    1. 对application注册了一个关于activity生命周期的回调callBack,在这个回调里面,LeakCanary只关注onDestroy方法,毕竟我们需要监测内存泄漏,肯定是从onDestroy方法开始的。
    2. 一旦一个activity执行了ondestroy方法,也就是这个activity需要被销毁时,此时就开始了LeakCanary的工作,会在callBack里面执行watch方法,也就是开始监测了这个activity被销毁的过程。
    3. 在watch方法里面,会生成一个带有自定义key值的弱引用,LeakCanary通过这个弱引用来监测是否被完全回收,对于android里面的弱引用的回收,当弱引用指向的对象被gc回收时,此时会将这个弱引用添加到一个对应的队列里面。
    4. 假如watch监测出来可能出现内存溢出,此时就会利用一个叫做haha的库,这个库是回来解析heapDump文件的,然后通过这个文件分析是否真正的出现了内存溢出。出现溢出,生成溢出堆栈信息。

    LeakCanary的代码实现:

    RefWatcher的内部实现

    public static RefWatcher install(Application application) {
        return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
            .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
            .buildAndInstall();
      }
    

    在我们在application调用了install方法时,将会通过builder方法创建RefWatcher。

    • DisplayLeakService是一个服务类,主要是用来分析泄漏的结果
    • AndroidExcludedRefs排除android系统的bug 我们先看buildAndInstall的实现
     public RefWatcher buildAndInstall() {
        ...
        //创建RefWatcher实例
        RefWatcher refWatcher = build();
        if (refWatcher != DISABLED) {
          if (watchActivities) {
            //开启Activity监听
            ActivityRefWatcher.install(context, refWatcher);
          }
          if (watchFragments) {
            //开启Fragment监听
            FragmentRefWatcher.Helper.install(context, refWatcher);
          }
        }
        LeakCanaryInternals.installedRefWatcher = refWatcher;
        return refWatcher;
      }
    

    在这个方法中,会创建出RefWatcher,同时开启Activity和Fragment的泄漏的监听。
    首先,我们先看看如何开启Activity的监听。

    public static void install(Context context, RefWatcher refWatcher) {
        Application application = (Application) context.getApplicationContext();
        ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
    
        application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
      }
    

    其中,activityRefWatcher.lifecycleCallbacks为

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

    所以,对于Activity的监听,其实就是对application注册了LifeCycleCallback,在每一个watcheActivity的时候,都会移除上一次的注册。

     public void watchActivities() {
        // Make sure you don't get installed twice.
        stopWatchingActivities();
        application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
      }
    
      public void stopWatchingActivities() {
        application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
      }
    

    最后在onActivityDestroyed的回调,执行watch操作,进行内存泄漏的检测。
    其实fragment和activity的监听其实是非常类似的,只不过在onDestory回调到时,调用了RefWatcher的watchFragments方法

     private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
            new ActivityLifecycleCallbacksAdapter() {
              @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                for (FragmentRefWatcher watcher : fragmentRefWatchers) {
                  watcher.watchFragments(activity);
                }
              }
            };
    

    在这个watchFragments中,注册了fragment的监听。

    @Override public void watchFragments(Activity activity) {
        if (activity instanceof FragmentActivity) {
          FragmentManager supportFragmentManager =
              ((FragmentActivity) activity).getSupportFragmentManager();
          supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
        }
      }
    

    这个fragmentLifecycleCallbacks是fragment的lifeCycleCallback

    private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
          new FragmentManager.FragmentLifecycleCallbacks() {
            @Override public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
              refWatcher.watch(fragment);
            }
          };
    

    这样,也就是完成了对于fragment的监听,在onFragmentDestroyed调用时,也会调用watch方法进行检测内存泄漏,只不过和activity不一样的是,在这里传入的fragment实例。

    对于RefWatcher,这个类是LeakCanary的核心。我们可以先看看其中的一些成员变量

      //执行内存泄漏的检测
      private final WatchExecutor watchExecutor;
      //用于查询是否在查询中
      private final DebuggerControl debuggerControl;
      //处理Gc,执行gc操作
      private final GcTrigger gcTrigger;
      //Dump内存泄漏的堆文件
      private final HeapDumper heapDumper;
      private final HeapDump.Listener heapdumpListener;
      private final HeapDump.Builder heapDumpBuilder;
      //产生内存泄漏的key
      private final Set<String> retainedKeys;
      //引用队列
      private final ReferenceQueue<Object> queue;
    

    在catch方法执行检测

    //这个方法会在任意一个activity的ondestroy方法调用时调用
    public void watch(Object watchedReference, String referenceName) {
        //通过这个Disable参数可以仅在debug包中开启。
        if (this == DISABLED) {
          return;
        }
        //引用和引用名称不能为空
        checkNotNull(watchedReference, "watchedReference");
        checkNotNull(referenceName, "referenceName");
        //记录监测的开始时间
        final long watchStartNanoTime = System.nanoTime();
        //生成一个随机的key,保证每次生成的弱引用的key都是不同的
        String key = UUID.randomUUID().toString();
        //添加到维护的key数组里面去,这是一个写时复制的数组,这个数组是用来记录LeakCanary生成的弱引用所对应的key,也就是记录监测的activity的key
        retainedKeys.add(key);
        //在这里生成弱引用,这个是LeakCanary自定义的,添加了key和引用名称,这个queue便是弱引用指向的对象被正确回收时会被添加到的队列。
        final KeyedWeakReference reference =
            new KeyedWeakReference(watchedReference, key, referenceName, queue);
        //开始监测这个引用的回收
        ensureGoneAsync(watchStartNanoTime, reference);
      }
    

    上面的方法,除了最后一句话,其他的都是在做一些初始化操作而已,ensureGoneAsync这个方法才是执行的监测的入口,这个是一个同步方法,它会利用watchExecutor去顺序的执行每一个弱引用的回收确认:

    private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
        //excutor监测池,持续监测这个弱引用是否被回收
        watchExecutor.execute(new Retryable() {
          @Override public Retryable.Result run() {
            return ensureGone(reference, watchStartNanoTime);
          }
        });
      }
    

    通过了run方法的执行,开始通过ensureGone方法去确认每一个弱引用的回收,在阅读这个关键的方法时,我们需要先来了解两个方法removeWeaklyReachableReferences和isGone:

    private void removeWeaklyReachableReferences() {
        //这个会发生在finalization和垃圾收集器之前
        KeyedWeakReference ref;
        while ((ref = (KeyedWeakReference) queue.poll()) != null) {
          retainedKeys.remove(ref.key);
        }
      }
    =====>
    public Reference<? extends T> poll() {
            synchronized (lock) {
                //queue的头为空,表示没有弱引用所指向的对象被回收
                if (head == null)
                    return null;
    
                return reallyPollLocked();
            }
        }
    =====>
    private Reference<? extends T> reallyPollLocked() {
            if (head != null) {
                //获取当前head头
                Reference<? extends T> r = head;
                //头等于尾,清空
                if (head == tail) {
                    tail = null;
                    head = null;
                } else {
                    获取下一个元素
                    head = head.queueNext;
                }
                //设置queueNext属性,标记一个引用有进队或者出队操作
                r.queueNext = sQueueNextUnenqueued;
                return r;
            }
    
            return null;
        }
    

    这个removeWeaklyReachableReferences方法会从前面指定的队列去获取是否存在被回收的弱引用,假如存在,则把我们保存的key数组中移除这个被成功回收的key值,剩下的就是可能存在泄漏的弱引用的key值,知道了这个,下面看看isGone的判断就比较简单了

    private boolean gone(KeyedWeakReference reference) {
        return !retainedKeys.contains(reference.key);
      }
    

    只需要判断retainedKeys数组中是否包含该引用的key就能知道有没有被回收了。下面来看看这这个ensureGone方法,看看是如何确认有没有造成内存泄漏的

    Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
        long gcStartNanoTime = System.nanoTime();
        long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
        //通过回收队列的引用去移除我们的key数组
        removeWeaklyReachableReferences();
        
        if (debuggerControl.isDebuggerAttached()) {
          return RETRY;
        }
        //如果被标记的弱引用均已被回收,那么返回。
        if (gone(reference)) {
          return DONE;
        }
        //在这里进行二次确认,手动执行一次gc
        gcTrigger.runGc();
        //在gc完成之后,再此通过queue移除我们的key数组。
        removeWeaklyReachableReferences();
        if (!gone(reference)) {
          //仍然存在没被回收的弱引用,此时可能造成内存泄漏,我们需要通过heapDump文件来分析
          long startDumpHeap = System.nanoTime();
          long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
          //根据现在内存,生成dump文件
          File heapDumpFile = heapDumper.dumpHeap();
          if (heapDumpFile == RETRY_LATER) {
            //目前不能读取,待重试状态
            return RETRY;
          }
          long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
          //heapdumpListener分析这个heapDump文件,判断是否泄漏。
          heapdumpListener.analyze(
              new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
                  gcDurationMs, heapDumpDurationMs));
        }
        return DONE;
      }
    

    当有可能造成内存泄漏的时候,会通过heap内存文件分析是否真正的出现了内存泄漏,会将这个文件的分析转交给分析类去处理,而Watcher在此时就完成了自己的工作。
    所以,watcher类主要做的事情就是在每一个ondestory调用时定义一个相对应的弱引用,并在gc后分析queue和key数组,判断是否可能造成泄漏,在有可能造成泄漏的时候,将dump文件的分析转交给其他类去处理。

    Dump

    我们看看dump文件分析的主要代码,首先watcher会通过listener将dump文件转交给监听,下面是这个listener的调用链:

    heapdumpListener.analyze(
              new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
                  gcDurationMs, heapDumpDurationMs));
    
    public void analyze(HeapDump heapDump) {
            Preconditions.checkNotNull(heapDump, "heapDump");
            HeapAnalyzerService.runAnalysis(this.context, heapDump, this.listenerServiceClass);
        }
    

    HeapAnalyzeService是一个IntentService,它有自己的工作线程,在这个工作线程执行完毕之后,会关闭自身service。

    public static void runAnalysis(Context context, HeapDump heapDump, Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
            Intent intent = new Intent(context, HeapAnalyzerService.class);
            intent.putExtra("listener_class_extra", listenerServiceClass.getName());
            intent.putExtra("heapdump_extra", heapDump);
            context.startService(intent);
        }
    

    工作线程的执行,

    protected void onHandleIntent(Intent intent) {
            if(intent == null) {
                CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.", new Object[0]);
            } else {
                String listenerClassName = intent.getStringExtra("listener_class_extra");
                HeapDump heapDump = (HeapDump)intent.getSerializableExtra("heapdump_extra");
                //将dump文件的分析交给了HeapAnalyzer类
                HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs);
                //获取泄漏的结果。
                AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
                AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
            }
        }
    

    通过着listener会开启一个IntentService,intentService与普通的service最主要的区别就是拥有自己的工作线程,而且待工作线程执行完毕之后,会自己关闭service,不用我们去手动进行控制。在HeapAnalyzerService中,又通过HeapAnalyzer去分析弱引用。我们来看看checkForLeak这个方法:

    public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
          ...
          //heap文件是.hprof后缀格式的
          HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
          //获取解析器,这个解析器库haha所提供的
          HprofParser parser = new HprofParser(buffer);
          //生成此时的内存快照
          Snapshot snapshot = parser.parse();
          
          //复制gc的根对象,这个root是用来判断可达性的,从root到我们的弱引用所指向的对象,
          deduplicateGcRoots(snapshot);
    
          //寻找泄漏的引用
          Instance leakingRef = findLeakingReference(referenceKey, snapshot);
    
          if (leakingRef == null) {
            return noLeak(since(analysisStartNanoTime));
          }
    
          return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef);
        }
    

    首先会生成内存快照,然后通过此时的快照来获取所有的根节点,通过这个根节点来判断弱引用指向对象的可达性。我们看看寻找泄漏引用的逻辑:

    private Instance findLeakingReference(String key, Snapshot snapshot) {
        //通过自定义弱引用对象的name获取该对象
        ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
        List<String> keysFound = new ArrayList<>();
        //获取当前的实例列表
        for (Instance instance : refClass.getInstancesList()) {
          //获取实例的参数域
          List<ClassInstance.FieldValue> values = classInstanceValues(instance);
          //获取key值
          String keyCandidate = asString(fieldValue(values, "key"));
          if (keyCandidate.equals(key)) {
            //如果需要寻找的key和获取出来的对象的key值一致,表示寻找到了这个弱引用,即有泄漏。
            return fieldValue(values, "referent");
          }
          keysFound.add(keyCandidate);
        }
        throw new IllegalStateException(
            "Could not find weak reference with key " + key + " in " + keysFound);
      }
    

    如果返回的不为空,那么返回寻找到的泄漏的弱引用对象。

    相关文章

      网友评论

          本文标题:LeakCanary详解

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