Leakcanary(二)

作者: 小水neo | 来源:发表于2020-12-18 15:48 被阅读0次

    Leakcanary图标的由来

    Leaknary

    17世纪,英国矿井工人发现,金丝雀对瓦斯这种气体十分敏感。空气中哪怕有极其微量的瓦斯,金丝雀也会停止歌唱;而当瓦斯含量超过一定限度时,虽然鲁钝的人类毫无察觉,金丝雀却早已毒发身亡。

    当时在采矿设备相对简陋的条件下,工人们每次下井都会带上一只金丝雀作为“瓦斯检测指标”,以便在危险状况下紧急撤离

    Leaknary的源码解析

    由于最新版本的Leaknary均是由Kotlin所实现的,阅读起来相对困难,因此本文的分析主要是基于Leaknary1.6版本的,并在最后简单介绍新版本对旧版本的优化。

    整体概览

    最新版的Leaknary,包大约有30M,其源码文件的结构如下图所示


    image.png

    其整体的调用流程参时序图:


    Leaknary时序图

    下面将详细展开

    入口函数

      public static RefWatcher install(Application application) {
         // 创建 AndroidRefWatcherBuilder
        return refWatcher(application)
        // 设置用于监听内存泄漏的分析结果的 Service
        .listenerServiceClass(DisplayLeakService.class)
         // 忽略检测的引用
         .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
          // 构建并且初始化 RefWatcher
         .buildAndInstall();
      }
    
    

    buildAndInstall

    public RefWatcher buildAndInstall() {
        if (LeakCanaryInternals.installedRefWatcher != null) {
          throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
        }
      // 创建最终返回的 RefWatcher
        RefWatcher refWatcher = build();
        if (refWatcher != DISABLED) {
          if (watchActivities) {
     // 如果需要监测 activity 则启动 activity 的监测
            ActivityRefWatcher.install(context, refWatcher);
          }
    // 同理Fragments检测
          if (watchFragments) {
            FragmentRefWatcher.Helper.install(context, refWatcher);
          }
        }
        LeakCanaryInternals.installedRefWatcher = refWatcher;
        return refWatcher;
      }
    

    build

      public final RefWatcher build() {
        if (isDisabled()) {
          return RefWatcher.DISABLED;
        }
    
        if (heapDumpBuilder.excludedRefs == null) {             // excludedRefs排除可以忽略的泄漏路径
          heapDumpBuilder.excludedRefs(defaultExcludedRefs());
        }
    
        HeapDump.Listener heapDumpListener = this.heapDumpListener;// 解析完 hprof 文件并通知 DisplayLeakService 弹出提醒
        if (heapDumpListener == null) {
          heapDumpListener = defaultHeapDumpListener();
        }
    
        DebuggerControl debuggerControl = this.debuggerControl;//判断是否处于调试模式,调试模式中不会进行内存泄漏检测
        if (debuggerControl == null) {
          debuggerControl = defaultDebuggerControl();
        }
    
        HeapDumper heapDumper = this.heapDumper;// dump 内存泄漏处的 heap 信息,写入 hprof 文件
        if (heapDumper == null) {
          heapDumper = defaultHeapDumper();
        }
    
        WatchExecutor watchExecutor = this.watchExecutor; //  线程控制器,在 onDestroy() 之后并且主线程空闲时执行内存泄漏检测
        if (watchExecutor == null) {
          watchExecutor = defaultWatchExecutor();
        }
    
        GcTrigger gcTrigger = this.gcTrigger;//gcTrigger 用于 GC,watchExecutor 首次检测到可能的内存泄漏,会主动进行 GC,GC 之后会再检测一次,仍然泄漏的判定为内存泄漏,进行后续操作
        if (gcTrigger == null) {
          gcTrigger = defaultGcTrigger();
        }
    
        if (heapDumpBuilder.reachabilityInspectorClasses == null) {
          heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
        }
    
        return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
            heapDumpBuilder);
      }
    

    Install

     public static void install(Context context, RefWatcher refWatcher) {
        Application application = (Application) context.getApplicationContext();
        ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
    
        application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
      }//lifecycleCallbacks 监听了 Activity 的各个生命周期,在 onDestroy() 中开始检测当前 Activity 的引用。
    ...
            @Override public void onActivityDestroyed(Activity activity) {
              refWatcher.watch(activity);
            }
    ...
    

    watch

      public void watch(Object watchedReference) {
        watch(watchedReference, "");
      }
    
      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);//: 一个 Set<String> 集合,每个检测的对象都对应着一个唯一的 key,存储在 retainedKeys 中
        final KeyedWeakReference reference =// 自定义的弱引用,持有检测对象和对用的 key 值
            new KeyedWeakReference(watchedReference, key, referenceName, queue);
        //重点的判断泄露的过程
        ensureGoneAsync(watchStartNanoTime, reference);
      }
    

    ensureGoneAsync

      private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
        watchExecutor.execute(new Retryable() {
          @Override public Retryable.Result run() {
            return ensureGone(reference, watchStartNanoTime);
          }
        });
      }//是一个异步任务,不会对主线程造成阻塞;
    

    ensureGone

      Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
        long gcStartNanoTime = System.nanoTime();
        long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    
        removeWeaklyReachableReferences();
        //遍历引用队列 queue,判断队列中是否存在当前 Activity 的弱引用,存在则删除 retainedKeys 中对应的引用的 key值。
    
        if (debuggerControl.isDebuggerAttached()) {
          // The debugger can create false leaks. 调试器可能直接产生内存泄露
          return RETRY;
        }
        if (gone(reference)) {
          return DONE;
        }
        //如果不包含,说明上一步操作中 retainedKeys 移除了该引用的 key 值,也就说上一步操作之前引用队列 queue 中包含该引用,GC 处理了该引用,未发生内存泄漏,返回 DONE,不再往下执行
        //如果包含,并不会立即判定发生内存泄漏,可能存在某个对象已经不可达,但是尚未进入引用队列 queue。这时会主动执行一次 GC 操作之后再次进行判断。
        gcTrigger.runGc();// 在进行一次gc
        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);//进行泄漏分析(内存泄漏最短路径分析)
        }
        //如果不包含,说明上一步操作中 retainedKeys 移除了该引用的 key 值,也就说上一步操作之前引用队列 queue 中包含该引用,GC 处理了该引用,未发生内存泄漏,返回 DONE,不再往下执行
        return DONE;
      }
    

    heapdumpListener.analyze

    由ServiceHeapDumpListener所实现, 在方法HeapAnalyzerService.runAnalysis的startForegroundService中的onHandleIntentInForeground的checkForLeak

     //在dump的堆信息中通过key查找出内存泄漏的弱引用对象,并且计算出最短的GC路径;
      public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey,
          boolean computeRetainedSize) {
        long analysisStartNanoTime = System.nanoTime();
    
        if (!heapDumpFile.exists()) {
          Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
          return failure(exception, since(analysisStartNanoTime));
        }
    
        try {
          listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
          HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
          HprofParser parser = new HprofParser(buffer);
          listener.onProgressUpdate(PARSING_HEAP_DUMP);
          Snapshot snapshot = parser.parse();  //生成文件的快照
          listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
          deduplicateGcRoots(snapshot);//过滤重复的内存泄漏对象
          listener.onProgressUpdate(FINDING_LEAKING_REF);
          Instance leakingRef = findLeakingReference(referenceKey, snapshot);
          //在快照中根据referenceKey查找是否有对应的内存泄漏对象
          // False alarm, weak reference was cleared in between key check and heap dump.
          if (leakingRef == null) {
            return noLeak(since(analysisStartNanoTime));   //不存在内存泄漏的情况
          }
          //则进入下一步查找内存泄漏对象的GC最短路径
          return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
        } catch (Throwable e) {
          return failure(e, since(analysisStartNanoTime));
        }
      }
    
    

    findLeakTrace

    进一步跟踪

     private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
          Instance leakingRef, boolean computeRetainedSize) {
    
        listener.onProgressUpdate(FINDING_SHORTEST_PATH);
        // 1. 寻找最短路径, 通过node.parent记忆当前的路径
        ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
        ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
    
        // False alarm, no strong reference path to GC Roots.
        if (result.leakingNode == null) {
          return noLeak(since(analysisStartNanoTime));
        }
    
        listener.onProgressUpdate(BUILDING_LEAK_TRACE);
        LeakTrace leakTrace = buildLeakTrace(result.leakingNode);
        // 2. 构建 LeakTrace
    
        String className = leakingRef.getClassObj().getClassName();
    
        // 计算3.  retained size
        long retainedSize;
        if (computeRetainedSize) {
    
          listener.onProgressUpdate(COMPUTING_DOMINATORS);
          // Side effect: computes retained size.
          snapshot.computeDominators();
    
          Instance leakingInstance = result.leakingNode.instance;
    
          retainedSize = leakingInstance.getTotalRetainedSize();
    
          // TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
          if (SDK_INT <= N_MR1) {
            listener.onProgressUpdate(COMPUTING_BITMAP_SIZE);
            retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
          }
        } else {
          retainedSize = AnalysisResult.RETAINED_HEAP_SKIPPED;
        }
        
        // 4. 构建最终的结果 AnalysisResult
        return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
            since(analysisStartNanoTime));
      }
    

    findPath

     Result findPath(Snapshot snapshot, Instance leakingRef) {
      //第一个参数是带有所有信息的snapshot对象,第二个参数是内存泄漏的那个类的封装对象
        clearState();
        canIgnoreStrings = !isString(leakingRef);
    
        enqueueGcRoots(snapshot);
        // 将上面找到的所有GC Roots添加到队列中
    
        boolean excludingKnownLeaks = false;
        LeakNode leakingNode = null;
        // 如果将从GC Root开始的所有引用看做树,则这里就可以理解成使用广度优先搜索遍历引用“森林”
        while (!toVisitQueue.isEmpty() || !toVisitIfNoPathQueue.isEmpty()) {
          LeakNode node;
          if (!toVisitQueue.isEmpty()) {
            node = toVisitQueue.poll();   //继续遍历
          } else {
           // 如果toVisitQueue中没有元素,则取toVisitIfNoPathQueue中的元素
            // 意思就是,如果遍历完了toVisitQueue还没有找到泄露的路径,那么就继续遍历设置了“例外”的那些对象
            // “例外”是什么情况?在后续两个方法会讲到。
            node = toVisitIfNoPathQueue.poll();
            if (node.exclusion == null) {
                
              throw new IllegalStateException("Expected node to have an exclusion " + node);
            }
            excludingKnownLeaks = true;
          }
    
          // Termination 终止
          if (node.instance == leakingRef) {
            leakingNode = node;
            break;
          }
          // 因为一个对象可以被多个对象引用,以GC Root为根的引用树
          // 并不是严格意义上的树,所以如果已经遍历过当前对象,就跳过
          if (checkSeen(node)) {
            continue;
          }
          // 下面是根据当前引用节点的类型,分别找到它们所引用的对象
    
          if (node.instance instanceof RootObj) {
            visitRootObj(node);
          } else if (node.instance instanceof ClassObj) {
            visitClassObj(node);
          } else if (node.instance instanceof ClassInstance) {
            visitClassInstance(node);  // 点进去继续,还没完
          } else if (node.instance instanceof ArrayInstance) {
            visitArrayInstance(node);
          } else {
            throw new IllegalStateException("Unexpected type for " + node.instance);
          }
        }
        return new Result(leakingNode, excludingKnownLeaks);
      }
    

    处理例外visitClassInstance

     private void visitClassInstance(LeakNode node) {
        ClassInstance classInstance = (ClassInstance) node.instance;
        // 将设置了“例外”的对象记录下来
        // 这里的“例外”就是上一个方法中提到的“例外”。是指那些低优先级的,或者说几乎不可能引发内存泄露的对象
        // 例如SDK中的一些对象,诸如Message, InputMethodManager等,一般情况下,这些对象都不会导致内存泄露。
        // 因此只有在遍历了其他对象之后,找不到泄露路径的情况下,才遍历这些对象。
    
        Map<String, Exclusion> ignoredFields = new LinkedHashMap<>();
        ClassObj superClassObj = classInstance.getClassObj();
        Exclusion classExclusion = null;
        while (superClassObj != null) {
          Exclusion params = excludedRefs.classNames.get(superClassObj.getClassName());
          if (params != null) {
            // true overrides null or false.
             // 如果当前类或者其父类被设置了“例外”,则将其赋值给classExclusion
            if (classExclusion == null || !classExclusion.alwaysExclude) {
              classExclusion = params;
            }
          }
          
          Map<String, Exclusion> classIgnoredFields =
              excludedRefs.fieldNameByClassName.get(superClassObj.getClassName());
          if (classIgnoredFields != null) {
            // 如果当前类及其父类包含例外的成员,将这些成员添加到ignoredFields中
            ignoredFields.putAll(classIgnoredFields);
          }
          superClassObj = superClassObj.getSuperClassObj();
        }
    
        if (classExclusion != null && classExclusion.alwaysExclude) {
          return;
        }
        // 遍历每一个成员
    
        for (ClassInstance.FieldValue fieldValue : classInstance.getValues()) {
          Exclusion fieldExclusion = classExclusion;
          Field field = fieldValue.getField();
          if (field.getType() != Type.OBJECT) {
             // 如果成员不是对象,则忽略
            continue;
          }
            // 获取成员实例
          Instance child = (Instance) fieldValue.getValue();
          String fieldName = field.getName();
          Exclusion params = ignoredFields.get(fieldName);
          // If we found a field exclusion and it's stronger than a class exclusion
          if (params != null && (fieldExclusion == null || (params.alwaysExclude
              && !fieldExclusion.alwaysExclude))) {
            // 如果当前成员对象是例外的,并且当前类和所有父类都不是例外的(classExclusion = null),
            // 或,如果当前成员对象时例外的,而且是alwaysExclude,而且当前类和父类都不是alwaysExclude
            // 则认为当前成员是需要例外处理的。
    
            fieldExclusion = params;
          }
          String value = fieldValue.getValue() == null ? "null" : fieldValue.getValue().toString();
          // 入队列
    
          enqueue(fieldExclusion, node, child, new LeakReference(INSTANCE_FIELD, fieldName, value));
        }
      }
    

    最后的细节 enqueue

    private void enqueue(Exclusion exclusion, LeakNode parent, Instance child,
          LeakReference leakReference) {
        if (child == null) {
          return;
        }
        if (isPrimitiveOrWrapperArray(child) || isPrimitiveWrapper(child)) {
          return;
        }
        // Whether we want to visit now or later, we should skip if this is already to visit.
        if (toVisitSet.contains(child)) {
          return;
        }
        // 这个exclusion就是上一个方法通过“很绕的”逻辑判断的出来的
        // 这里的作用就是如果为null则visitNow,这个boolean值在下面会用到。
        // 可以看到这里只是判断exclusion是否为null,并没有使用到alwaysExclude参数,
        // 所以说上一个方法中,“||”之后的判断是没有必要的。
    
        boolean visitNow = exclusion == null;
        if (!visitNow && toVisitIfNoPathSet.contains(child)) {
          return;
        }
        if (canIgnoreStrings && isString(child)) {
          return;
        }
        if (visitedSet.contains(child)) {
          return;
        }
        LeakNode childNode = new LeakNode(exclusion, child, parent, leakReference);
        // 这里用到了boolean值visitNow,就是说如果exclusion对象为null,则表示这不是一个例外的对象(暂且称之为常规对象);
        if (visitNow) {
        // 如果exclusion对象不为null,则表示这个对象是例外对象,只有在遍历所有常规对象之后,还是找不到路径的情况下才会被遍历。
          toVisitSet.add(child);
          toVisitQueue.add(childNode);
        } else {
        
          toVisitIfNoPathSet.add(child);
          toVisitIfNoPathQueue.add(childNode);
        }
      }
    
    image.png

    总结

    1. 从逻辑上Leaknary可以划分为RefWatcher和Analyzer两个部分,其中Refwatcher用于检测内存泄露,analyzer用于分析内存泄露的最短路径,并在app中显示(调用了第三方库)。
    2. Leaknary检测内存泄露的原理是:弱引用在一次 gc 后便会进入到内存回收的状态,在对象被GC的同时,会把该对象的包装类即weakReference放入到ReferenceQueue,通过 poll 操作就可以取出这个弱引用,从retainedKeys中移除这个 对应的key, 代表这个对象已经被正常回收,不需要再被监视了。而如果在ActivityDestroy的时候,ensureGone发现有对象没有被回收,,则主动 GC 一下,然后再次确认是否被回收,如果第二次确认时对象没有被回收则判断为内存泄露。
    3. 为什么ensureGone中要gc两次?
      因为,判断为异步操作,可能存在某个对象已经不可达,但是尚未进入引用队列 queue。这时会主动执行一次 GC 操作之后再次进行判断。
    4. 整体流程图总结


      执行流程图
    1. Leakcanary的缺点
      虽然 LeakCanary 有诸多优点,但是它也有做不到的地方,比如说检测申请大容量内存导致的OOM问题、Bitmap内存未释放问题,Service 中的内存泄漏可能无法检测等。

    版本变化

    1. 2.0之前生成分析报告用的是haha第三方库,2.0之后改用了shark,在生成heap分析报告时,内存使用变小,速度加快
    2. 2.0之后的Leaknary无需再程序员手动将初始化方法加入程序中,只需要注入依赖即可,其原理是Leakanary通过在ContentProvider回调的时候做了初始化。

    相关文章

      网友评论

        本文标题:Leakcanary(二)

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