LeakCanary 源码分析

LeakCanary 源码分析

    • 一、概述
    • 二、原理
    • 三、源码分析
      • 1. LeakCanary 的初始化
      • 2. Activity 的监测
      • 3. 如何判断观察对象是否内存泄漏

一、概述

在 Android 开发过程中会遇到内存问题,在内存问题的分析中有一项必不可少的环节是对 hprof 文件的分析,常见的 MAT 和 Leakcanry 都是针对hprof文件的分析工具。

本文我们来分析一下 Leakcanry 的检测原理。


二、原理

弱引用: 在垃圾回收时,无论内存是否充足,都会将弱引用包装的对象回收。

当JVM进行垃圾回收时,无论内存是否充足,如果该对象只有弱引用存在,那么该对象会被垃圾回收器回收,同时该引用会被加入到关联的ReferenceQueue。因此程序可以通过判断引用队列中是否已经包含指定的引用,来了解被引用的对象是否被GC回收 (引用队列中存在指定的弱引用,说明对象被回收)。

所以 Leakcanary 在进行内存泄露的监控时,利用弱引用的上述特性,在对象生命周期结束后主动gc并检查该对象的弱引用是否被回收,如果弱引用没有被正常回收,说明在对象生命周期结束以后,该对象还被其他对象持有他的非弱引用。该对象还有到达GC ROOTS的可达路径。如果在对象生命周期结束后弱引用不存在了,说明该对象已经被JVM的垃圾回收器正常回收了,该对象的内存空间也被正常回收。


三、源码分析

1. LeakCanary 的初始化

我们先从入口开始

LeakCanary

// LeakCanary.class
public static @NonNull RefWatcher install(@NonNull Application application) {
  // 构建一个 AndroidRefWatcherBuilder 对象,并设置一系列参数,最终用于构建 RefWatcher 对象。
  return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
      .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
      .buildAndInstall();
}

小结:
这里通过 Builder 模式构建一个 RefWatcher 对象,这个类就是用来监测指定对象内存是否泄漏的类。同时给这个 Builder 设置一些参数 (如:系统内存泄漏的信息 AndroidExcludedRefs、分析内存泄漏原因的 DisplayLeakService)。

下面我们来看下 buildAndInstall() 做了哪些操作。

AndroidRefWatcherBuilder

// AndroidRefWatcherBuilder.class
public @NonNull RefWatcher buildAndInstall() {
  if (LeakCanaryInternals.installedRefWatcher != null) {
    throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
  }
  // build 里面会做很多初始化操作。
  RefWatcher refWatcher = build();
  if (refWatcher != DISABLED) {
    if (enableDisplayLeakActivity) {
      LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
    }
    if (watchActivities) {
      // 检测 Activity 的内存泄漏
      ActivityRefWatcher.install(context, refWatcher);
    }
    if (watchFragments) {
      // 检测 Fragment 的内存泄漏(包括V4包的Fragment)
      FragmentRefWatcher.Helper.install(context, refWatcher);
    }
  }
  LeakCanaryInternals.installedRefWatcher = refWatcher;
  return refWatcher;
}

RefWatcherBuilder
build() 方法里面会做很多对象的初始化操作,了解就行。

// RefWatcherBuilder.class
public final RefWatcher build() {
  if (isDisabled()) {
    return RefWatcher.DISABLED;
  }

  if (heapDumpBuilder.excludedRefs == null) {
    heapDumpBuilder.excludedRefs(defaultExcludedRefs());
  }

  HeapDump.Listener heapDumpListener = this.heapDumpListener;
  if (heapDumpListener == null) {
    heapDumpListener = defaultHeapDumpListener();
  }

  DebuggerControl debuggerControl = this.debuggerControl;
  if (debuggerControl == null) {
    debuggerControl = defaultDebuggerControl();
  }

  HeapDumper heapDumper = this.heapDumper;
  if (heapDumper == null) {
    heapDumper = defaultHeapDumper();
  }

  WatchExecutor watchExecutor = this.watchExecutor;
  if (watchExecutor == null) {
    watchExecutor = defaultWatchExecutor();
  }

  GcTrigger gcTrigger = this.gcTrigger;
  if (gcTrigger == null) {
    gcTrigger = defaultGcTrigger();
  }

  if (heapDumpBuilder.reachabilityInspectorClasses == null) {
    heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
  }

  return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
      heapDumpBuilder);
}

2. Activity 的监测

在 LeakCanary 初始化过程中,我们知道 AndroidRefWatcherBuilder.buildAndInstall() 过程会调用 Activity监测的逻辑 ActivityRefWatcher.install(context, refWatcher)

ActivityRefWatcher

// ActivityRefWatcher.class
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
  Application application = (Application) context.getApplicationContext();
  ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
  // 通过 Application 注册 Activity 生命周期回调接口。
  application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}


private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
    new ActivityLifecycleCallbacksAdapter() {
      @Override public void onActivityDestroyed(Activity activity) {
      	// 在Activity 被销毁的时候去进行监听(Activity被销毁后,理应被回收,所以在这里触发监听操作)。
        refWatcher.watch(activity);
      }
    };

小结:

这里我们只要了解 Activity 是在 onDestroyed 生命周期里被监测的就可以了,下面开始分析核心逻辑。

3. 如何判断观察对象是否内存泄漏

RefWatcher.watch()RefWatcher.ensureGoneAsync()

// RefWatcher.class
private final Set<String> retainedKeys; //CopyOnWriteArraySet类型
// 引用队列,被回收的弱引用都会存入引用队列中,这是弱引用的特性。
private final ReferenceQueue<Object> queue; 

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();
  // 1.会为每一个被观察的对象生成一个 UUID 随机变量,并保存到 retainedKeys 中。
  String key = UUID.randomUUID().toString();
  retainedKeys.add(key);
  // 2.将被观察的对象包装成弱引用,并与key和引用对象作关联。
  final KeyedWeakReference reference =
      new KeyedWeakReference(watchedReference, key, referenceName, queue);
  // 3.然后执行被观察对象是否被回收的逻辑
  ensureGoneAsync(watchStartNanoTime, reference);
}

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
  // 注意:这里的 watchExecutor其实是 AndroidWatchExecutor,在主线程空闲时,才去异步执行 Retryable。
  watchExecutor.execute(new Retryable() {
    @Override public Retryable.Result run() {
      // 核心逻辑
      return ensureGone(reference, watchStartNanoTime);
    }
  });
}

小结:

这里的 watchExecutor其实是 AndroidWatchExecutor,在主线程空闲时,才去异步执行 Retryable。

问题: 如何实现主线程空闲再去异步执行的逻辑?
利用 MessageQueue.IdleHandler 在主线程空闲时才执行的特点,在 IdleHandler 中执行异步操作。


在分析 ensureGone() 方法之前,我们先来分析一下 RefWatcher.removeWeaklyReachableReferences() 方法的作用。

RefWatcher.removeWeaklyReachableReferences()

private void removeWeaklyReachableReferences() {
  KeyedWeakReference ref;
  while ((ref = (KeyedWeakReference) queue.poll()) != null) {
    // 将已经被回收的对象(即queue中的元素)对应的 key 从 retainedKeys 移除。
    retainedKeys.remove(ref.key);
  }
}

小结:

先回顾一下前面两个变量:retainedKeysReferenceQueue
retainedKeys: 每监测一个对象,就会生成一个 UUID 的随机变量 key,并存入 retainedKeys 中,说明监测的对象个数跟 retainedKeys 内元素的个数一样多。
ReferenceQueue: 每一个被回收的弱引用对象都会被放入 ReferenceQueue 中(这是虚拟机操作的)。
.
我们将 retainedKeys 看做是监测对象的总数,ReferenceQueue 看做是回收对象的数量,理论上如果没有内存泄漏(对retainedKeys 进行元素移除操作前),那么 retainedKeys 和 ReferenceQueue 内的元素个数应该相等,因此,可能内存泄漏的元素个数 = retainedKeys - ReferenceQueue
.
那么,如何表示这个减号的操作呢?使用元素移除操作 remove() 即可。


RefWatcher.ensureGone()
这是核心方法

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
  long gcStartNanoTime = System.nanoTime();
  long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
  // 1.先进行移除(可能这里已经发生过gc操作了)
  removeWeaklyReachableReferences();
  // Debug 模式忽略
  if (debuggerControl.isDebuggerAttached()) {
    // The debugger can create false leaks.
    return RETRY;
  }
  // 2.判断 retainedKeys 中是否存在指定的 key,gone()返回true 表示不存在。
  if (gone(reference)) {
    return DONE;
  }
  // 3.前面表示指定的弱引用没有被回收,因此手动触发GC操作。
  gcTrigger.runGc(); //内部触发 Runtime.getRuntime().gc() 操作,同时让当前线程sleep(100,给gc一段处理时间)。
  // 4.与步骤1相同的操作
  removeWeaklyReachableReferences();
  // 5.如果gone()返回false,表示可能存在内存泄漏。
  if (!gone(reference)) {
    long startDumpHeap = System.nanoTime();
    long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
	// 6.导出堆栈信息,内部其实会触发 Debug.dumpHprofData(heapDumpFile.getAbsolutePath()) 方法导出HProf文件。
    File heapDumpFile = heapDumper.dumpHeap();
    if (heapDumpFile == RETRY_LATER) {
      // Could not dump the heap.
      return RETRY;
    }
    long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
	// 7.将导出的信息封装成HeapDump对象
    HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
        .referenceName(reference.name)
        .watchDurationMs(watchDurationMs)
        .gcDurationMs(gcDurationMs)
        .heapDumpDurationMs(heapDumpDurationMs)
        .build();
	// 8.对HeapDump进行内存泄漏的分析
    heapdumpListener.analyze(heapDump); // heapdumpListeners是ServiceHeapDumpListener类型
  }
  return DONE;
}

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

小结:
监测逻辑分为8个步骤:

  1. 先进行移除(可能这里已经发生过gc操作了)。
  2. 判断 retainedKeys 中是否存在指定的 key,gone()返回true 表示不存在。
  3. 前面表示指定的弱引用没有被回收,因此手动触发GC操作。
  4. 与步骤1相同的操作。
  5. 如果gone()返回false,表示可能存在内存泄漏。
  6. 导出堆栈信息,内部其实会触发 Debug.dumpHprofData(heapDumpFile.getAbsolutePath()) 方法导出HProf文件。
  7. 将导出的信息封装成HeapDump对象。
  8. 对HeapDump进行内存泄漏的分析。

为何 步骤 1 , 2 和 步骤 4 , 5 重复?

条件的前置判断,避免不必要的gc操作,因为在步骤1操作的时候,系统可能已经gc过了。


HeapAnalyzerService

调用链:

RefWatcher.ensureGone()
–>ServiceHeapDumpListener.analyze(heapDump)
–> HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass)
–> HeapAnalyzerService.onHandleIntentInForeground(intent)

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);
  // 解析 heapDump.heapDumpFile 的文件信息
  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);
    HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
    HprofParser parser = new HprofParser(buffer);
    listener.onProgressUpdate(PARSING_HEAP_DUMP);
    // 1.将 heapDumpFile 生成 Snapshot。
    Snapshot snapshot = parser.parse();
    listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
    // 2.移除里面重复的 GcRoots
    deduplicateGcRoots(snapshot);
    listener.onProgressUpdate(FINDING_LEAKING_REF);
    // 3.查找可能泄漏的弱引用对象是否可以在snapshot中找到,如果无法,说明没有泄漏,若找到,则说明存在泄漏。
    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));
    }
    // 4.执行到这里说明监测的对象存在泄漏,因此查找该对象到GCRoot的最短引用路径(LeakTrace)。
    return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
  } catch (Throwable e) {
    return failure(e, since(analysisStartNanoTime));
  }
}

// 查找制定key的
private Instance findLeakingReference(String key, Snapshot snapshot) {
  // 1. 从snapshot中找到名称为 KeyedWeakReference 的 class 对象集。
  ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
  if (refClass == null) {
    throw new IllegalStateException(
        "Could not find the " + KeyedWeakReference.class.getName() + " class in the heap dump.");
  }
  List<String> keysFound = new ArrayList<>();
  // 2.从这个对象集中获取所有 KeyedWeakReference 实例对象
  for (Instance instance : refClass.getInstancesList()) {
    List<ClassInstance.FieldValue> values = classInstanceValues(instance);
    // 3.从KeyedWeakReference中获取key的值。
    Object keyFieldValue = fieldValue(values, "key");
    if (keyFieldValue == null) {
      keysFound.add(null);
      continue;
    }
    String keyCandidate = asString(keyFieldValue);
    // 4.将检测对象的key值与snapshot中所有KeyedWeakReference对象的key值进行比较,若相等,则表示存在泄漏。
    if (keyCandidate.equals(key)) {
      return fieldValue(values, "referent");
    }
    keysFound.add(keyCandidate);
  }
  throw new IllegalStateException(
      "Could not find weak reference with key " + key + " in " + keysFound);
}

小结:

checkForLeak() 的步骤:

  1. 将 heapDumpFile 生成 Snapshot。
  2. 移除里面重复的 GcRoots。
  3. 查找可能泄漏的弱引用对象是否可以在snapshot中找到,如果无法,说明没有泄漏,若找到,则说明存在泄漏。
  4. 执行到步骤4说明监测的对象存在泄漏,因此查找该对象到GCRoot的最短引用路径(LeakTrace)。

findLeakingReference() 的步骤:

  1. 从snapshot中找到名称为 KeyedWeakReference 的 class 对象集。
  2. 从这个对象集中获取所有 KeyedWeakReference 实例对象。
  3. 从KeyedWeakReference中获取key的值。
  4. 将检测对象的key值与snapshot中所有KeyedWeakReference对象的key值进行比较,若相等,则表示存在泄漏。

你可能感兴趣的:(开源框架源码分析)