LeakCannary使用方法及实现原理探究(二)—— LeakCannary实现原理及源码分析

写在前头

这里首先将LeakCannary的原理写在这里,让大家有一个初步的印象,然后我们再一步步去分析具体的实现逻辑

LeakCannary原理

1. Activity onDestroy之后将它放到一个WeakReference
2. 这个WeakReference关联到一个ReferenceQueue
3. 查看ReferenceQueue是否存在 Activity的引用
4. 如果该Acitivty泄漏了,Dump出内存信息,再去分析泄漏路径

Java的四种引用

首先我们先回顾一下Java的内存模型中的四种引用方式

  • 强引用,永远都不会被回收的内存,程序宁愿跑出oom也不回回收
  • 软引用,当内存不足时会进行回收(实际回收时会对内存分类标记,GC第一次运行会按优先级回收弱引用和虚引用,这时若内存还是不足,则GC会再执行一次,将软引用回收)
  • 弱引用,GC一开始工作就回回收
  • 虚引用,任何时候都有可能被回收

如何将activity加入监测

根据上一篇中提到的LeakCannary的使用方法我们知道,LeakCanary.install()是使用LeakCannary的一个前提,那我们就从这里开始入手分析,看LeakCannary如何一步步分析定位内存泄漏的。

public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }

该方法的返回值是一个RefWatcher,主要是通过buildAndInstall()来创建的,我们跟进去看一下

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

第二行有一个非空判断,LeakCanaryInternals.installedRefWatcher不能被重复创建,buildAndInstall()这个方法只能被调用一次,watchActivities和watchFragments的默认值都是true,表示会对Activity和Fragment进行监测,我们以Activity为例,跟进去看一看具体逻辑。

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

可以看到上面这个方法,首先获取了Application,然后注册了activity的生命周期监听,这个callback如下

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

可见是在activity ondestroy的时候,调用refWatcher监测这个activity。

RefWatcher watch的具体逻辑

首先我们对RefWatcher类中的几个主要成员变量做个简单介绍

private final WatchExecutor watchExecutor; //用于执行内存泄漏监测用的
private final DebuggerControl debuggerControl; //查询是否正在调试中,如果正在调试中,LeakCannary是不会进行内存泄漏检测的
private final GcTrigger gcTrigger; //进行GC的一些处理
private final HeapDumper heapDumper; //dump内存泄漏文件
private final HeapDump.Listener heapdumpListener;
private final HeapDump.Builder heapDumpBuilder;
private final Set<String> retainedKeys; //待检测的和产生内存泄漏的引用的key
private final ReferenceQueue<Object> queue; //引用队列,判断弱引用所持有的对象是否被执行了垃圾回收

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();//对reference添加一个为一点key值
  retainedKeys.add(key);
  final KeyedWeakReference reference =
      new KeyedWeakReference(watchedReference, key, referenceName, queue);
  ensureGoneAsync(watchStartNanoTime, reference);
}

将传入的watchedReference创建了一个我们所需要的弱引用,最后调用ensureGoneAsync开启异步线程,来对是否有内存泄漏进行分析。

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

这个方法其实主要是确保activity是否已经被回收了
watchDurationMs表示调用该方法到产生垃圾回收所用的时间

removeWeaklyReachableReferences()我们看一下,它的主要作用是,检查一下已经到达弱引用队列的对象,然后retainedKeys集合中对应的key移除掉,从而保证,retainedKeys剩余的key对应的引用还没有被回收。

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

我们接着回到上面的ensureGone方法,第5行和第9行,分别判断了是否正在调试,或者是否所有的reference都已经被回收,然后返回对应的值。

下面第12行调用了gcTrigger.runGc(),这里其实是手动调用了一次GC,为的是保证在进行后面的分析检测之前,GC已经执行过,并将可以被回收的对象进行了回收。然后下面紧接着,再一次调用了removeWeaklyReachableReferences()清除已经到达引用队列的弱引用。再下面经过if(gone())判断之后,就可以认为此时还存在于retainedKeys的key对应的引用,即是产生内存泄漏的对象。然后就开始调用analyze()方法来对内存泄漏进行分析。

这里我们先简单总结一下,LeakCannary做的几件事情:

1. 首先会创建一个refWatcher,启动一个ActivityRefWatcher
2. tongguo ActivityLifecycleCallbacks把Activity的onDestroy生命周期关联
3. 最后在线程池中去开始分析我们的泄漏

分析内存泄漏

analyze()是一个接口,它的实现在一个ServiceHeapDumpListener里面

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

我们跟进HeapAnalyzerService.runAnalysis()来看一下

public final class HeapAnalyzerService extends ForegroundService
        implements AnalyzerProgressListener {
        
        ...
        
        public static void runAnalysis(Context context, HeapDump heapDump,
                               Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    setEnabledBlocking(context, HeapAnalyzerService.class, true);
    setEnabledBlocking(context, listenerServiceClass, true);
    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,所以runAnalysis会回调到onHandleIntent,跟代码进去,发现经过封装会到onHandleIntentInForeground()这里。

@Override protected void onHandleIntent(@Nullable Intent intent) {
  onHandleIntentInForeground(intent);
}
protected abstract void onHandleIntentInForeground(@Nullable Intent intent);
@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);
    // checkForLeak很重要,用于进行分析
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
            heapDump.computeRetainedHeapSize);
    // 将分析结果进行显示
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}

checkForLeak对内存的分析

checkForLeak就是进一步分析内存,将前面创建好的hprof文件解析成Snapshot对象分析。

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);
    Snapshot snapshot = parser.parse();
    listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
    deduplicateGcRoots(snapshot);
    listener.onProgressUpdate(FINDING_LEAKING_REF);
    //根据referenceKey来查询结果中是否有我们需要的对象
    Instance leakingRef = findLeakingReference(referenceKey, snapshot);
    // False alarm, weak reference was cleared in between key check and heap dump.
    //对上面的结果进行判断,为空表示不存在
    if (leakingRef == null) {
      return noLeak(since(analysisStartNanoTime));
    }
    //不为空,则把路径找出来
    return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
  } catch (Throwable e) {
    return failure(e, since(analysisStartNanoTime));
  }
}

总结checkForLeak:

1. 把.hprof转为Snapshot
2. 优化GCRoots
3. 找出泄漏的对象,找出泄漏对象的最短路径

查找泄漏对象和路径的具体分析

查找泄漏对象

private Instance findLeakingReference(String key, Snapshot snapshot) {
    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<>();
    for (Instance instance : refClass.getInstancesList()) {
        List<ClassInstance.FieldValue> values = classInstanceValues(instance);
        Object keyFieldValue = fieldValue(values, "key");
        if (keyFieldValue == null) {
            keysFound.add(null);
            continue;
        }
        String keyCandidate = asString(keyFieldValue);
        if (keyCandidate.equals(key)) {
            return fieldValue(values, "referent");
        }
        keysFound.add(keyCandidate);
    }
    throw new IllegalStateException(
            "Could not find weak reference with key " + key + " in " + keysFound);
}

这个方法是通过下面三步来查找泄漏对象的:

  1. 在snapshot中找到第一个弱引用
  2. 遍历这个对象的所有实例
  3. 如果key值和最开始定义封装的key值相同,那么返回这个泄漏对象

查找泄漏路径

private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
                                     Instance leakingRef, boolean computeRetainedSize) {
    listener.onProgressUpdate(FINDING_SHORTEST_PATH);
    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);
    String className = leakingRef.getClassObj().getClassName();
    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;
    }
    return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
            since(analysisStartNanoTime));
}

总结LeakCannary原理

1. Activity onDestroy之后将它放到一个WeakReference
2. 这个WeakReference关联到一个ReferenceQueue
3. 查看ReferenceQueue是否存在 Activity的引用
4. 如果该Acitivty泄漏了,Dump出内存信息,再去分析泄漏路径

你可能感兴趣的:(Android,Android第三方源码,LeakCananry,内存泄漏,内存泄漏检测)