LeakCanary 原理详解

内存泄漏是性能优化中必须去关注的一个方面,LeakCanary 在发现内存泄漏问题上是一个优秀的工具,今天来分析下它内部的工作原理是怎样的。

首先来看几个问题:

  1. 集成 LeakCanary 后,安装应用到手机上,会发现桌面上多了一个 LeakCanary 的图标,这个图标是怎么来的呢
  2. 内存泄漏的问题是怎么检测到的呢

先来看第一个问题吧,这个比较简单,在调用 LeakCanary.install(this) 时,点进源码,会看到这样一行代码

enableDisplayLeakActivity(application);

这行代码最终执行的是

public static void setEnabledBlocking(Context appContext, Class<?> componentClass, boolean enabled) {
    ComponentName component = new ComponentName(appContext, componentClass);
    PackageManager packageManager = appContext.getPackageManager();
    int newState = enabled?1:2;
    packageManager.setComponentEnabledSetting(component, newState, 1);
}

这里的 componentClass 是 DisplayLeakActivity.class, 在 AndroidManifest.xml 文件中声明这个 Activity 时,设置 android:enabled="false", 然后再在代码中动态这样设置,就会在桌面上生成新的图标,作为 Activity 的入口。


接下来解答第二个问题,内存泄漏的问题是怎么检测到的,也就是说比如如何检测 Activity finish 后还没有被回收,这里 LeakCanary 用的方法是,记录所有的 Activity,在 Application 中注册 LeakCanary 时通过 registerActivityLifecycleCallbacks 监听 Activity 的生命周期,然后在 Activity 执行 onDestroy 时看 Activity 是否被回收,如果没有没回收,触发 gc, 再看有没有被回收,如果还没有被回收,那么就是有内存泄漏了,就收集内存泄漏相关日志信息。
大概的流程就是这样子了,具体细节,如何检测 Activity 是否被回收,如何触发了 gc, 看下 LeakCanary 的实现方式的主要代码:

public void watch(Object watchedReference, String referenceName) {
    //...
    if(!this.debuggerControl.isDebuggerAttached()) {
        final long watchStartNanoTime = System.nanoTime();
        String key = UUID.randomUUID().toString();
        this.retainedKeys.add(key);
        // watchedReference 执行过onDrstroy的Activity的引用,key为随机数,queue 是一个ReferenceQueue对象,在引用中用于记录回收的对象
        final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, this.queue);
        this.watchExecutor.execute(new Runnable() {
            public void run() {
                // 方法最后执行到这里
                RefWatcher.this.ensureGone(reference, watchStartNanoTime);
            }
        });
    }
}
// reference 这是一个弱引用
void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = TimeUnit.NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    // 这个方法做的操作是 有被回收的,从集合中移除 reference.key
    // 这个方法里就利用的 Reference.queue, 从 queue 里面取出来说明是被回收的
    this.removeWeaklyReachableReferences();
    if(!this.gone(reference) && !this.debuggerControl.isDebuggerAttached()) {
        // 进到 if 里面说明还没有被回收
        // 触发 gc, 这里触发 gc 的方式是调用 Runtime.getRuntime().gc();
        this.gcTrigger.runGc();
        // 重新把已回收的 key 从集合中 remove 掉
        this.removeWeaklyReachableReferences();
        if(!this.gone(reference)) {
            // 进到 if 里,说明还没有被回收,gc后还没有被回收,说明这是回收不了的了,也就是发生了内存泄漏了
            long startDumpHeap = System.nanoTime();
            long gcDurationMs = TimeUnit.NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
            // 收集 hprof 文件
            File heapDumpFile = this.heapDumper.dumpHeap();
            if(heapDumpFile == HeapDumper.NO_DUMP) {
                return;
            }

            long heapDumpDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
            // 解析泄漏日志,通知有泄漏,并保存到本地
            this.heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key, reference.name, this.excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs));
        }

    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章