LeakCanary原理分析

概述

上一篇LeakCanary使用详细教程中,我们熟悉了LeakCanary的使用和初步描述了它的工作机制,这篇我准备从源码的角度去分析LeakCanary的工作原理;

源码分析

从上一篇中我们知道,LeakCanary在Appaction的初始化方式:

@Override
public void onCreate() {
    super.onCreate();
    if (LeakCanary.isInAnalyzerProcess(this)) {
        // This process is dedicated to LeakCanary for heap analysis.
        // You should not init your app in this process.
        return;
    }
    LeakCanary.install(this);
}

其实LeakCanary.install(this)这句调用之后LeakCanary已经可以正常监控应用中所有Activity的内存泄漏情况了,那它是怎么做到的呢?我们带着这个问题来看一下LeakCanary的源码:

install()方法
  /**
   * Creates a {@link RefWatcher} that works out of the box, and starts watching activity
   * references (on ICS+).
   */
  public static @NonNull RefWatcher install(@NonNull Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }
  
  public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
      return new AndroidRefWatcherBuilder(context);
  }
要点分析:
1、install是有返回值的—RefWatcher,这个对象非常重要,LeakCanary就是通过这个对象进行Activity的内存监控
2、RefWatcher是通过Builder模式创建的;
3、install内部一连串的链式调用,最后调用了buildAndInstall()方法;
  private boolean watchActivities = true;
  private boolean watchFragments = true;

  /**
   * Creates a {@link RefWatcher} instance and makes it available through {@link
   * LeakCanary#installedRefWatcher()}.
   *
   * Also starts watching activity references if {@link #watchActivities(boolean)} was set to true.
   *
   * @throws UnsupportedOperationException if called more than once per Android process.
   */
  public @NonNull RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    RefWatcher refWatcher = build();//RefWatch就是在这边创建的
    if (refWatcher != DISABLED) {
      LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }
要点分析:
1、LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);允许泄漏报告页面的显示;
2、watchActivities/watchFragments默认值都是true,所以ActivityRefWatcher和FragmentRefWatcher都将被启用;
3、ActivityRefWatcher用于监控Activity的内存泄漏;
4、FragmentRefWatcher用于监控Fragment的内存泄漏;

我们就用ActivityRefWatcher来进一步分析LeakCanary是如何监控内存泄漏的:

ActivityRefWatcher.install(context, refWatcher)方法:
  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);
  }
要点分析:
1、registerActivityLifecycleCallbacks是Android Application的一个方法,注册了该方法,应用中每一个Activity的生命周期变化都会通过该方法回调回来;
2、registerActivityLifecycleCallbacks方法传入的参数是activityRefWatcher.lifecycleCallbacks,我们到ActivityRefWatcher中看下该方法的实现:
    private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityDestroyed(Activity activity) {
          refWatcher.watch(activity);
        }
      };

从上面的实现来看,ActivityRefWatcher只监听了onActivityDestroyed方法,也就是说每一个Activity调用onDestory的时候都会回调到onActivityDestroyed这个方法,通知LeakCanary
该Activity应该被销毁;

到这来我们知道了为什么只执行LeakCanary.install(this)一条语句就可以完成对整个app的Activity内存泄漏进行监听了;

接下来我们来看看ActivityRefWatcher是如何监听内存泄漏的,RefWatcher有两个核心方法: watch() 和 ensureGone()

watch方法:
/**
   * Watches the provided references and checks if it can be GCed. This method is non blocking,
   * the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
   * with.
   *
   * @param referenceName An logical identifier for the watched object.
   */
  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);

    ensureGoneAsync(watchStartNanoTime, reference);
  }
要点分析:
1、首先我们来翻译一下,官方对watch方法的注释:

监听提供的引用,检查该引用是否可以被回收。这个方法是非阻塞的,因为检测功能是在Executor中的异步线程执行的

2、checkNotNull:分别检测watchedReference、referenceName是否为空,如果为空则抛出异常结束;
3、随机生成一个key,该key用于唯一标识已产生内存泄漏的对象,或者准备检测的对象;
4、创建KeyedWeakReference对象,并调用另一个核心方法ensureGone;
  private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }


@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
  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);
    }
    return DONE;
  }
要点分析:
1、ensureGone方法是在ensureGoneAsync方法中开启的,并且是一个异步任务,不会对主线程造成阻塞;
2、removeWeaklyReachableReferences():移除已经回收的弱引用对象的key,如果需要判断的对象已经销毁,则不继续执行
if (gone(reference)) {
      return DONE;
    }
3、如果移除之后还是存在该引用则手动再GC一次:gcTrigger.runGc();
4、然后二次调用removeWeaklyReachableReferences()再判断是否已经销毁;
5、如果二次确认还是存在则判断为内存泄漏了开始.hprof文件的dump;
6、调用heapdumpListener.analyze(heapDump)进行泄漏分析;

阶段总结一下:

1. 弱引用与ReferenceQueue联合使用,如果弱引用关联的对象被回收,则会把这个弱引用加入到ReferenceQueue中;通过这个原理,可以看出removeWeaklyReachableReferences()执行后,会对应删除KeyedWeakReference的数据。如果这个引用继续存在,那么就说明没有被回收。
2. 为了确保最大保险的判定是否被回收,一共执行了两次回收判定,包括一次手动GC后的回收判定。两次都没有被回收,很大程度上说明了这个对象的内存被泄漏了,但并不能100%保证;因此LeakCanary是存在极小程度的误差的。

hprof文件已经生成好了,接下来就是进行内存泄漏最短路径分析了:

上一步heapdumpListener.analyze(heapDump)的调用即为分析入口:

  @Override public void analyze(@NonNull HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }
要点分析:
1、调用了HeapAnalyzerService,在单独的进程中进行分析;

我们直接把HeapAnalyzerService整个类的代码贴上去:

public final class HeapAnalyzerService extends ForegroundService
    implements AnalyzerProgressListener {

  private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
  private static final String HEAPDUMP_EXTRA = "heapdump_extra";

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

  public HeapAnalyzerService() {
    super(HeapAnalyzerService.class.getSimpleName(), R.string.leak_canary_notification_analysing);
  }

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

  @Override public void onProgressUpdate(Step step) {
    int percent = (int) ((100f * step.ordinal()) / Step.values().length);
    CanaryLog.d("Analysis in progress, working on: %s", step.name());
    String lowercase = step.name().replace("_", " ").toLowerCase();
    String message = lowercase.substring(0, 1).toUpperCase() + lowercase.substring(1);
    showForegroundNotification(100, percent, false, message);
  }
}
要点分析:
1、runAnalysis方法中最后一句:ContextCompat.startForegroundService(context, intent)启动HeapAnalyzerService
2、HeapAnalyzerService继承自ForegroundService,服务启动时会调用onHandleIntentInForeground方法;

onHandleIntentInForeground方法中比较核心的方法调用是:

    HeapAnalyzer heapAnalyzer =
            new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
    
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
            heapDump.computeRetainedHeapSize);

HeapAnalyzer中核心的方法是checkForLeak,我们来看下它的具体实现:

  /**
   * Searches the heap dump for a {@link KeyedWeakReference} instance with the corresponding key,
   * and then computes the shortest strong reference path from that instance to the GC roots.
   */
  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);
      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));
    }
  }
要点分析:
1、先来翻译一下官方对该方法的注释:

在dump的堆信息中通过key查找出内存泄漏的弱引用对象,并且计算出最短的GC路径;

2、Snapshot snapshot = parser.parse()–生成文件的快照
3、deduplicateGcRoots(snapshot)–过滤重复的内存泄漏对象;
4、findLeakingReference(referenceKey, snapshot)–在快照中根据referenceKey查找是否有对于的内存泄漏对象,如果获取到的leakingRef为空,则说明内存已经被回收了,不存在内存泄漏的情况;如果leakingRef不为空则进入下一步查找内存泄漏对象的GC最短路径;
5、findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize)–查找内存泄漏对象的GC最短路径;

好了LeakCanary的核心源码已经都已经分析完了,剩下的就是如果在页面上显示出来了,这部分就不在这边讨论了,有兴趣的同学可以下载LeakCanary的源码看一下;

回顾一下我们刚刚分析过的重要的类和方法

方法备注
LeakCanaryinstall()SDK初始化接口,通常我们只需要
调用该方法就可以监控app的
内存泄漏情况;
DisplayLeakActivity内存泄漏的查看显示页面
RefWatcherwatch()
ensureGone()
watch()–监听提供的引用,检查该引用是
否可以被回收。这个方法是非阻塞的
,因为检测功能是在Executor中
的异步线程执行的;
ensureGone()–2次确认是否确实
存在内存泄漏,如果存在内存泄漏则
,dump泄漏信息到hprof文件中,并调用ServiceHeapDumpListener
回调进行内存分析;
ActivityRefWatcherActivity引用检测, 包含了Activity生命
周期的监听执行与停止
HeapAnalyzerServicerunAnalysis内存堆分析服务, 为了保证App进程
不会因此受影响
变慢&内存溢出,运行于独立的进程
HeapAnalyzercheckForLeak在dump的堆信息中通过key查找
出内存泄漏的弱引用对象,并
且计算出最短的GC路径

总结

1、LeakCanary主要是通过Application的registerActivityLifecycleCallbacks方法监控每一个Activty的Destory之后对象是否被收回
2、在Activity Destory之后ActivityRefWatch的watch方法将被调用,watch方法会通过一个随机生成的key将这个弱引用关联到一个ReferenceQueue,然后调用ensureGone();

当一个软引用/弱引用对象被垃圾回收后,Java虚拟机就会把这个引用加入到与之关联的引用队列中;

3、ActivityRefWatch的ensureGone()方法中会先确认一次是否已经被回收,如果发现没有被回收,则主动GC一下,然后在次确认是否被回收,如果还是没有回收则判断为内存泄漏;
4、一旦确认是内存泄漏,则开始dump信息到hprof文件中,并调用heapdumpListener.analyze(heapDump)开始内存分析;
5、内存分析是在HeapAnalyzerService服务中进行的,属于一个单独的进程;
6、HeapAnalyzerService的runAnalysis中创建HeapAnalyzer对象并调用它的一个核心方法checkForLeak();
7、HeapAnalyzer的checkForLeak()会先解析hprof文件并生成快照文件,然后对快照中的泄漏对象进行去重,去重后根据第2步中的key去获取泄漏对象,如果对象为空则说明对象已经被回收,如果不为空则通过findLeakTrace()方法计算出最短GC路径,并显示到DisplayLeakActivity页面,提醒开发者存在内存泄漏;

上一篇:LeakCanary使用详细教程

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值