LeakCanary 原理分析

LeakCanary 原理分析

Java引用

1.强引用(当我们创建一个对象时,默认创建的就是强引用。只要强引用还存在,垃圾回收器就算抛出OOM,也不会回收强引用引用的对象。)
2.软引用(SoftReference,当内存不足时垃圾回收器会回收被引用的对象)
3.弱引用(WeakReference,当GC时无论内存是否足够垃圾回收器都会回收掉被引用的对象)
4.虚引用 (PhantomReference,基本不会用到。)

怎么使用?

LeakCanary.refWatcher(application)
            .watchFragments(false)
            .listenerServiceClass(LeakReportService::class.java)
            .excludedRefs(ExcludedRefs.create())
            .buildAndInstall()

非常标准的建造者模式,分别设置观察对象、是否监听 fragment 的内存泄漏,检测到内存泄漏时的自定义处理者、排除掉的引用

源码解析

  public @NonNull RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      if (enableDisplayLeakActivity) {
        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      }
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

我们重点分析 ActivityRefWatcher 其他的原理相同

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

    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }

lifecycleCallbacks 的实现如下

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

可以看到 LeakCanary 只关注 Activity destroy 的回调,在此时来进行内存泄漏判断

RefWatcher 的实现如下

  private final Set<String> retainedKeys = new CopyOnWriteArraySet<>();

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

    watchExecutor.execute(new Runnable() {
      @Override public void run() {
        ensureGone(reference, watchStartNanoTime);
      }
    });
  }

CopyOnWriteArraySet是一个支持并发访问的可去重数据类,内部对并发访问冲突的场景做了特殊适配,时间复杂度是O(n),没有直接用 synchronizedSet 的 HashSet 应该是为了解决空间,与服务端相比,客户端更关注空间复杂度,更愿意用时间换空间:

public boolean add(E e) {
    return al.addIfAbsent(e);
}

public boolean addIfAbsent(E e) {
    Object[] snapshot = getArray();
    return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
        addIfAbsent(e, snapshot);
}

private boolean addIfAbsent(E e, Object[] snapshot) {
    synchronized (lock) {
        Object[] current = getArray();
        int len = current.length;
        if (snapshot != current) {
            // 此时并发访问存在冲突,检查是否元素 e 已经加入到数组中
            int common = Math.min(snapshot.length, len);
            for (int i = 0; i < common; i++)
                if (current[i] != snapshot[i] && Objects.equals(e, current[i]))
                    return false;
            if (indexOf(e, current, common, len) >= 0)
                    return false;
        }
        Object[] newElements = Arrays.copyOf(current, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    }
}

randomUUID是一个随机数生成器,生成的是安全随机数,普通的 Random 是可被猜测的

核心是这句:

final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);

此时传入了一个自定义的 ReferenceQueue

queue = new ReferenceQueue<>();

this method is invoked only by Java code; when the garbage collector enqueues references it does so directly, without invoking this method.

在垃圾回收器回收该弱引用的时候会将该值加入到我们指定的这个队列中,但不是通过调用该函数加入的

    public boolean enqueue() {
       return queue != null && queue.enqueue(this);
    }

queue 内部实现是靠链表实现的

private boolean enqueueLocked(Reference<? extends T> r) {
        // Verify the reference has not already been enqueued.
        if (r.queueNext != null) {
            return false;
        }

        if (r instanceof Cleaner) {
            // If this reference is a Cleaner, then simply invoke the clean method instead
            // of enqueueing it in the queue. Cleaners are associated with dummy queues that
            // are never polled and objects are never enqueued on them.
            Cleaner cl = (sun.misc.Cleaner) r;
            cl.clean();

            // Update queueNext to indicate that the reference has been
            // enqueued, but is now removed from the queue.
            r.queueNext = sQueueNextUnenqueued;
            return true;
        }

        if (tail == null) {
            head = r;
        } else {
            tail.queueNext = r;
        }
        tail = r;
        tail.queueNext = r;
        return true;
    }

ensureGoneAsync 的实现

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

    // 此时先根据 queue 内存已存在的值清空一次 retainedKeys 
    removeWeaklyReachableReferences();

    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    // 如果此时 activity 已经被回收掉了,就不属于内存泄漏了
    if (gone(reference)) {
      return DONE;
    }
    // 如果此时 gc 没有被触发,可能 activity 确实已经没有强引用了,但是还没被回收,因此我们手动触发一次 gc
    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;
  }

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

heapDumper 的具体实现在 AndroidHeapDumper


  @Override public File dumpHeap() {
    // 没有权限的话现申请权限 
    if (!leakDirectoryProvider.isLeakStorageWritable()) {
      CanaryLog.d("Could not write to leak storage to dump heap.");
      leakDirectoryProvider.requestWritePermissionNotification();
      return NO_DUMP;
    }
    File heapDumpFile = getHeapDumpFile();
    boolean fileCreated;
    try {
      fileCreated = heapDumpFile.createNewFile();
    } catch (IOException e) {
      cleanup();
      CanaryLog.d(e, "Could not check if heap dump file exists");
      return NO_DUMP;
    }

    if (!fileCreated) {
      CanaryLog.d("Could not dump heap, previous analysis still is in progress.");
      return NO_DUMP;
    }

    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 NO_DUMP;
    }

    Toast toast = waitingForToast.get();
    try {
      Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
      cancelToast(toast);
      return heapDumpFile;
    } catch (Exception e) {
      cleanup();
      CanaryLog.d(e, "Could not perform heap dump");
      // Abort heap dump
      return NO_DUMP;
    }
  }

实现 heapdumpListener.analyze(heapDump); 的是 HeapAnalyzerService

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

  @Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
    if (intent == null) {
      CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
      return;
    }
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);

    HeapAnalyzer heapAnalyzer =
        new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);

    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
        heapDump.computeRetainedHeapSize);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }

分析内存泄漏链由 HeapAnalyzer 实现

  public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,
      @NonNull 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);
      // 具体的文件解析由 haha 库完成,这里不做特殊分析
      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);

      // False alarm, weak reference was cleared in between key check and heap dump.
      if (leakingRef == null) {
        String className = leakingRef.getClassObj().getClassName();
        return noLeak(className, since(analysisStartNanoTime));
      }
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }

引用链查找是在 findPath 中实现的

  Result findPath(Snapshot snapshot, Instance leakingRef) {
    clearState();
    canIgnoreStrings = !isString(leakingRef);

    // 将 GcRoots 节点全部加入队列
    enqueueGcRoots(snapshot);

    boolean excludingKnownLeaks = false;
    LeakNode leakingNode = null;
    while (!toVisitQueue.isEmpty() || !toVisitIfNoPathQueue.isEmpty()) {
      LeakNode node;
      if (!toVisitQueue.isEmpty()) {
        node = toVisitQueue.poll();
      } else {
        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;
      }

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

这里的代码比较长,简单来说就是先将 GcRoots 加入到队列中,然后依次从队列里取出需要分析的节点,将引用该节点强引用的节点再加入到队列中,节点信息的解析是通过 haha 库实现的,这里不做过多的分析了。
总体来说就是最标准的 bfs 代码

来执行泄漏链可视化任务的是 AbstractAnalysisResultService

  @Override protected final void onHandleIntentInForeground(@Nullable Intent intent) {
    if (intent == null) {
      CanaryLog.d("AbstractAnalysisResultService received a null intent, ignoring.");
      return;
    }
    if (!intent.hasExtra(ANALYZED_HEAP_PATH_EXTRA)) {
      onAnalysisResultFailure(getString(R.string.leak_canary_result_failure_no_disk_space));
      return;
    }
    File analyzedHeapFile = new File(intent.getStringExtra(ANALYZED_HEAP_PATH_EXTRA));
    AnalyzedHeap analyzedHeap = AnalyzedHeap.load(analyzedHeapFile);
    if (analyzedHeap == null) {
      onAnalysisResultFailure(getString(R.string.leak_canary_result_failure_no_file));
      return;
    }
    try {
      onHeapAnalyzed(analyzedHeap);
    } finally {
      //noinspection ResultOfMethodCallIgnored
      analyzedHeap.heapDump.heapDumpFile.delete();
      //noinspection ResultOfMethodCallIgnored
      analyzedHeap.selfFile.delete();
    }
  }

实现 onHeapAnalyzed 的是 DisplayLeakService


  @Override
  protected final void onHeapAnalyzed(@NonNull AnalyzedHeap analyzedHeap) {
    HeapDump heapDump = analyzedHeap.heapDump;
    AnalysisResult result = analyzedHeap.result;

    String leakInfo = leakInfo(this, heapDump, result, true);
    CanaryLog.d("%s", leakInfo);

    heapDump = renameHeapdump(heapDump);
    boolean resultSaved = saveResult(heapDump, result);

    String contentTitle;
    if (resultSaved) {
      PendingIntent pendingIntent =
          DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
      if (result.failure != null) {
        contentTitle = getString(R.string.leak_canary_analysis_failed);
      } else {
        String className = classSimpleName(result.className);
        if (result.leakFound) {
          if (result.retainedHeapSize == AnalysisResult.RETAINED_HEAP_SKIPPED) {
            if (result.excludedLeak) {
              contentTitle = getString(R.string.leak_canary_leak_excluded, className);
            } else {
              contentTitle = getString(R.string.leak_canary_class_has_leaked, className);
            }
          } else {
            String size = formatShortFileSize(this, result.retainedHeapSize);
            if (result.excludedLeak) {
              contentTitle =
                  getString(R.string.leak_canary_leak_excluded_retaining, className, size);
            } else {
              contentTitle =
                  getString(R.string.leak_canary_class_has_leaked_retaining, className, size);
            }
          }
        } else {
          contentTitle = getString(R.string.leak_canary_class_no_leak, className);
        }
      }
      String contentText = getString(R.string.leak_canary_notification_message);
      showNotification(pendingIntent, contentTitle, contentText);
    } else {
      onAnalysisResultFailure(getString(R.string.leak_canary_could_not_save_text));
    }

    afterDefaultHandling(heapDump, result, leakInfo);
  }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值