美文网首页
Leakcanary-流程分析篇

Leakcanary-流程分析篇

作者: 小寒飞刀 | 来源:发表于2018-12-26 09:39 被阅读0次

    一. 概述:
    流程分为三块:
    1.监听
    2.检测泄露
    3.分析

    监听
    在Android中,当一个Activity走完onDestroy生命周期后,说明该页面已经被销毁了,应该被系统GC回收。通过Application.registerActivityLifecycleCallbacks()方法注册Activity生命周期的监听,每当一个Activity页面销毁时候,获取到这个Activity去检测这个Activity是否真的被系统GC。

    检测
    当获取了待分析的对象后,需要确定这个对象是否产生了内存泄漏。
    通过WeakReference + ReferenceQueue来判断对象是否被系统GC回收,WeakReference 创建时,可以传入一个 ReferenceQueue 对象。当被 WeakReference 引用的对象的生命周期结束,一旦被 GC 检查到,GC 将会把该对象添加到 ReferenceQueue 中,待ReferenceQueue处理。当 GC 过后对象一直不被加入 ReferenceQueue,它可能存在内存泄漏。
    当我们初步确定待分析对象未被GC回收时候,手动触发GC,二次确认。

    分析
    分析这块使用了Square的另一个开源库haha,https://github.com/square/haha,利用它获取当前内存中的heap堆信息的快照snapshot,然后通过待分析对象去snapshot里面去查找强引用关系。

    二. 源码流程
    1.监听:
    在Application里加入代码:

    if (LeakCanary.isInAnalyzerProcess(this)) {
                        // This process is dedicated to LeakCanary for heap analysis.
                        // You should not init your app in this process.
                        return;
                    }
                    LeakCanary.install(getApplication());
    

    然后, install里调用的是:

    if (refWatcher != DISABLED) {
          if (watchActivities) {
            ActivityRefWatcher.install(context, refWatcher);
          }
          if (watchFragments) {
            FragmentRefWatcher.Helper.install(context, refWatcher);
          }
        }
    

    所以, Application里默认会放对Activity和Fragment的监控。

    放的过程比较有意思:
    先: application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
    在这里lifecycleCallbacks的定义是:

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

    Application里这个Callback的调用时机是:
    在Activity.java里, 每个方法比如onCreate里,会有调用:
    getApplication().dispatchActivityCreated(this, savedInstanceState);
    而dispatchActivityCreated里就会调用自己的callback相应的代码

    2 检测
    在onActivityDestroy里调用的是:refWatcher.watch(activity);
    watch的主要工作就是这个:

    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);
    

    对每一个监控的对象, 随机生成一个key,放在retainedKeys(private final Set<String> retainedKeys;)中。这个是作为以后对象是否还存活的依据。
    这里的关键点在创建KeyedWeakReference的时候, 传入了queue。这个是WeakReference的一个特有功能,如果创建一个弱引用, 并且传入一个queue的话, 那么当gc回收这个弱引用的时候, 会将相应的弱引用放在传入的queue中。这个很重要!

    接下来, 看ensureGoneAsync的实现:

    Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
        long gcStartNanoTime = System.nanoTime();
        long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    
        removeWeaklyReachableReferences();
    
        if (debuggerControl.isDebuggerAttached()) {
          // The debugger can create false leaks.
          return RETRY;
        }
        if (gone(reference)) {
          return DONE;
        }
        gcTrigger.runGc();
        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;
      }
    

    其中的removeWeaklyReachableReferences就是

    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;
        while ((ref = (KeyedWeakReference) queue.poll()) != null) {
          retainedKeys.remove(ref.key);
        }
      }
    

    根据gc回收时放入queue里的弱引用,将刚才生成key时放入的retainKeys里的被回收的对象的key去掉。
    而怎么根据queue来找到key呢?就是在创建弱引用的时候, 将key作为参数传给了弱引用并且存下来当做一个属性。

    下面继续看ensureGone的步骤:
    remove后retainkeys依然包含我们监控的key的话, 就继续执行gc(gc这里也有一点值得注意的, 放在gc篇里了)。然后看reference是否依然包含key:

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

    然后,就检测到有内存泄露了。下面是分析的步骤

    1. 分析
      File heapDumpFile = heapDumper.dumpHeap(); 先拿到dump文件。
      AndroidHeapDumper.java中的dumpHeap:
    @Override
     public File dumpHeap() {
        File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
    
        if (heapDumpFile == RETRY_LATER) {
          return RETRY_LATER;
        }
    
        FutureResult<Toast> waitingForToast = new FutureResult<>();
        showToast(waitingForToast);
    
        if (!waitingForToast.wait(5, SECONDS)) {
          CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
          return RETRY_LATER;
        }
    
        Notification.Builder builder = new Notification.Builder(context)
            .setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
        Notification notification = LeakCanaryInternals.buildNotification(context, builder);
        NotificationManager notificationManager =
            (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        int notificationId = (int) SystemClock.uptimeMillis();
        notificationManager.notify(notificationId, notification);
    
        Toast toast = waitingForToast.get();
        try {
          Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
          cancelToast(toast);
          notificationManager.cancel(notificationId);
          return heapDumpFile;
        } catch (Exception e) {
          CanaryLog.d(e, "Could not dump heap");
          // Abort heap dump
          return RETRY_LATER;
        }
      }
    

    dump生成hprof文件是调用:Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
    这是集成在vm中的一个功能,不做详细讨论,大致就是把当前的内存状态(对象引用关系)放到一个hprof文件中。生成文件后,弹窗消失。

    然后就是生成一个HeapDump的对象, 用heapdumpListner:heapdumpListener.analyze(heapDump);
    ServiceHeapDumpListener.java:

    @Override public void analyze(HeapDump heapDump) {
        checkNotNull(heapDump, "heapDump");
        HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
      }
    

    而这里的HeapAnalyzerService是一个IntentService。在这个新启动的service中执行对hprof文件的分析查找, 最后打印出泄露栈。具体HeapAnalyzerService的工作流程, 会在HeapAnalyzerService篇讲。

    参考文章:
    https://allenwu.itscoder.com/leakcanary-source
    https://blog.csdn.net/xiaohanluo/article/details/78196755
    https://allenwu.itscoder.com/leakcanary-source
    https://ivanljt.github.io/blog/2017/12/15/%E6%8B%86%E8%BD%AE%E5%AD%90%E7%B3%BB%E5%88%97%E2%80%94%E2%80%94LeakCanary%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86/
    https://cloud.tencent.com/developer/article/1327724

    相关文章

      网友评论

          本文标题:Leakcanary-流程分析篇

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