LeakCannay 原理浅析

·  阅读 833

前言

内存优化是 Android 开发无法避免的一个问题,而内存泄漏又让开发者花费了大量的时间,那怎样才能快速定位内存泄漏了?对于内存泄漏的检测,有 Android Studio 自带的 Profiler,以及 LeakCanary等。

由于在项目中使用了 LeakCanary,很好奇它的工作原理,它是如何对对象的泄漏进行检测的?什么样的对象判定为已经发生了内存泄漏?

可达性分析算法

可达性分析算法是理解 LeakCanary 的前置知识,它可以用来判断一个对象是否是垃圾,如果是垃圾的话,需要进行垃圾回收。之前在写 JVM 的时候讲到过,总结地说:这是一种垃圾检测的方法,内存泄漏就是一个成为垃圾的对象,没有办法被垃圾回收器回收。

LeakCanary 工作原理

一、添加依赖

使用 LeakCanary 需要添加依赖如下:

debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
releaseImplementation   'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'

自定义 Application,在 onCreate() 中需要对 LeakCanary 进行构建:

public void onCreate() {
    super.onCreate();
    // 内存泄漏的检测需要运行在一个独立的进程,如果当前进程不在 LeakCanary 进程,则需要进行初始化
    if (LeakCanary.isInAnalyzerProcess(this)) {
        return;
    }
    // 关键代码,构建了 RefWatcher 对象
    LeakCanary.install(this);
}

当然,如果构建的是 LeakCanary 新版本的依赖,已经不需要添加如上代码。

二、构建 RefWatcher 对象

截屏2021-12-23 20.27.47.png

具体又可以分为以下几个步骤:

  • refWatcher(): 构建一个 AndroidRefWatcherBuilder 对象
  • listenerServiceClass(): 传入展示分析结果的 Service
  • excludedRefs(): 排除 Android SDK 或者一些第三方库导致的内存泄漏
  • buildAndInstall(): 构建 RefWatcher 对象,开始对应用的进行内存泄漏的监听。

其中最核心的是在第4步,下面俩看具体的代码实现。

三、监听内存泄漏

public @NonNull RefWatcher buildAndInstall() {
  // ...
  // 1、构建 RefWatcher 对象
  RefWatcher refWatcher = build();
  if (refWatcher != DISABLED) {
    LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
    // 2、监听所有的 Activity
    if (watchActivities) {
      ActivityRefWatcher.install(context, refWatcher);
    }
    // 3、监听所有的 Fragment
    if (watchFragments) {
      FragmentRefWatcher.Helper.install(context, refWatcher);
    }
  }
  LeakCanaryInternals.installedRefWatcher = refWatcher;
  return refWatcher;
}

接下来重点关注 Activity 的内存泄漏的分析。

四、监听 Activity 内存泄漏

public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
  Application application = (Application) context.getApplicationContext();
  ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
  //通过调用 registerActivityLifecycleCallbacks() 来监听 Activity 的生命周期
  application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}

传入的这个回调如下,可以看到实际上调用了 RefWatcher 的 watch 方法:

截屏2021-12-23 21.48.44.png

接下来看 refWatcher.watch(activity) 的具体实现:

public void watch(Object watchedReference, String referenceName) {
  // 空值检查...
  final long watchStartNanoTime = System.nanoTime();
  String key = UUID.randomUUID().toString();
  retainedKeys.add(key);
  // 使用 key 和 watchedReference 构造一个 KeyedWeakReference 弱引用队列
  final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
  // 监测是否发生了内存泄漏
  ensureGoneAsync(watchStartNanoTime, reference);
}

这里涉及到了弱引用相关的知识,监测的原理就是利用了 Java 的 WeakReference 和 ReferenceQueue,如果被监测的这个 Activity 是引用可达的,那么就会被放入 ReferenceQueue 中,通过监测 ReferenceQueue 是否存在该 Activity 就可以知道是否发生了内存泄漏(泄漏的这个 Activity 没有被回收,依然被其他对象所引用着,那么就不能在引用队列中找到这个 Activity,因此就可以判断这个 Activity 发生了内存泄漏)。

具体的监测细节策略在 ensureGone() 中实现:

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
  // 1、如果存在引用队列中,说明已经被 GC 回收了,手动移除 retainedKeys 中的 key
  removeWeaklyReachableReferences();
  if (debuggerControl.isDebuggerAttached()) {
    return RETRY;//debug 不进行检测分析
  }
  // 2、如果 retainedKeys 不存在该引用对应的 key 了,说明已经被垃圾回收,直接返回
  if (gone(reference)) {
    return DONE;
  }
  // 3、手动触发 GC,检查该引用是否能被 GC 回收,回收的话会放到引用队列里
  gcTrigger.runGc();
  // 如果引用队列存在,需要删除 retainedKeys 中该引用对应的 key
  removeWeaklyReachableReferences();
  // 4、如果 retainedKeys 还存在 Activity 对应的 key,则说明发生了内存泄漏,生成堆内存快照,分析快照
  if (!gone(reference)) {
    //
    // 5、生成 HeapDump 文件
    File heapDumpFile = heapDumper.dumpHeap();
    //...
    // 6、生成 HeapDump 对象
    HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
        .referenceName(reference.name)
        .watchDurationMs(watchDurationMs)
        .gcDurationMs(gcDurationMs)
        .heapDumpDurationMs(heapDumpDurationMs)
        .build();
    // 7、调用 HeapAnalyzerService,开启后台执行分析任务
    heapdumpListener.analyze(heapDump);
  }
  return DONE;
}

这里的 heapdumpListener 实际上是 ServiceHeapDumpListener,在它的 analyze() 内部调用了 HeapAnalyzerService#runAnalysis():

public static void runAnalysis(Context context, HeapDump heapDump,
    Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
  setEnabledBlocking(context, HeapAnalyzerService.class, true);
  setEnabledBlocking(context, listenerServiceClass, true);
  // 启动 HeapAnalyzerService
  Intent intent = new Intent(context, HeapAnalyzerService.class);
  intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
  intent.putExtra(HEAPDUMP_EXTRA, heapDump);
  ContextCompat.startForegroundService(context, intent);
}

HeapAnalyzerService 继承自 IntentService,它被启动后会在子线程中执行具体的任务,即调用 onHandleIntentInForeground,任务执行完成后会自动销毁,接下来看具体的流程:

@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
  String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
  HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
  // 生成 HeapAnalyzer对象
  HeapAnalyzer heapAnalyzer =
      new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
  // 分析对内存快照,找出 GC root 最短引用路径
  AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
      heapDump.computeRetainedHeapSize);
  // 启动 DisplayLeakService 记录日志和展示通知
  AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
分类:
Android
标签:
分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改