LeakCanary内存泄漏检测机制原理

917 阅读5分钟

LeakCanary是Square公司基于MAT开发的一个用于检测内存泄露的库,它能检测应用存在的内存泄露,并通过界面直观的展示给开发者,极大的方便调试和开发健壮可靠的程序。

内存泄露

内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。比如在c++中通过new的内存忘记delete掉,或者java中对象使用完成后没有释放对象引用d导致GC不能正常的回收该对象而继续占用内存。

使用

LeakCanary的使用也很简单,首先在gradle文件中添加

    dependencies {
        debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3'
        releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3'
    }

然后在Application的onCreate中调用如下代码就可以使用LeakCanary了

LeakCanary.install(this);

检测原理

下面我们就从install方法开始阅读下LeakCanary的源码,从而理解LeakCanary的检测原理。

public static RefWatcher install(Application application) {
    return install(application, DisplayLeakService.class, AndroidExcludedRefs.createAppDefaults().build());
}

public static RefWatcher install(Application application, Class<? extends AbstractAnalysisResultService> listenerServiceClass, ExcludedRefs excludedRefs) {
    if(isInAnalyzerProcess(application)) {
        return RefWatcher.DISABLED;
    } else {
        enableDisplayLeakActivity(application);
        ServiceHeapDumpListener heapDumpListener = new ServiceHeapDumpListener(application, listenerServiceClass);
        RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);
        ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
        return refWatcher;
    }
}

LeakCanary的install方法先判断当前进程是否在LeakCanary所在进程,因为LeakCanary分析时是在独立的进程中进行的,Applciation会有多个实例分别对应不同的进程,如果在的话就直接返回RefWatcher.DISABLED,否则就是在我们的自己的App进程中,这里首先启用DisplayLeakActivity这个页面最终会显示内存泄露的情况,随后创建一个ServiceHeapDumpListener,这个类实现了HeapDump的Listener接口,这个接口提供了analyze方法,这个方法从来分析dump下来的Mat文件。

public static RefWatcher androidWatcher(Context context, Listener heapDumpListener, ExcludedRefs excludedRefs) {
    AndroidDebuggerControl debuggerControl = new AndroidDebuggerControl();
    AndroidHeapDumper heapDumper = new AndroidHeapDumper(context);
    heapDumper.cleanup();
    return new RefWatcher(new AndroidWatchExecutor(), debuggerControl, GcTrigger.DEFAULT, heapDumper, heapDumpListener, excludedRefs);
}

随后通过androidWatcher创建一个RefWatcher,这个是用来分析内存泄露用的。

public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
    if(VERSION.SDK_INT >= 14) {
        ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
        activityRefWatcher.watchActivities();
    }
}
接下来还创建了一个ActivityRefWatcher,这watcher用来监听Activity的生命周期。这个是通过watchActivities来完成的。

public void watchActivities() {
    this.stopWatchingActivities();
    this.application.registerActivityLifecycleCallbacks(this.lifecycleCallbacks);
}

watchActivities会通过registerActivityLifecycleCallbacks注册Ativity生命周期的回调,这个方法会通过回调lifecycleCallbacks监听应用中所有activity的生命周期。

private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacks() {
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    }

    public void onActivityStarted(Activity activity) {
    }

    public void onActivityResumed(Activity activity) {
    }

    public void onActivityPaused(Activity activity) {
    }

    public void onActivityStopped(Activity activity) {
    }

    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    }

    public void onActivityDestroyed(Activity activity) {
        ActivityRefWatcher.this.onActivityDestroyed(activity);
    }
};

void onActivityDestroyed(Activity activity) {
    this.refWatcher.watch(activity);
}

可以看到在生命周期回调lifecycleCallbacks中,只在onActivityDestroyed中进行了处理,这里会调用RefWather的watch方法来分析是否有内存泄露。

public void watch(Object watchedReference) {
    this.watch(watchedReference, "");
}

public void watch(Object watchedReference, String referenceName) {
    Preconditions.checkNotNull(watchedReference, "watchedReference");
    Preconditions.checkNotNull(referenceName, "referenceName");
    if(!this.debuggerControl.isDebuggerAttached()) {
        final long watchStartNanoTime = System.nanoTime();
        String key = UUID.randomUUID().toString();
        this.retainedKeys.add(key);
        final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, this.queue);
        this.watchExecutor.execute(new Runnable() {
            public void run() {
                RefWatcher.this.ensureGone(reference, watchStartNanoTime);
            }
        });
    }
}

watch方法会将当前Activity实例引用封装为一个KeyedWeakReference,从名字来看它是一个WeakRefrence,同时对应一个key,key是一个UUID,同时这个key是被添加在retainedKeys中,它是一个CopyOnWriteArraySet。需要注意的是在创建KeyedWeakReference,我们提供了一个ReferenceQueue队列,这样当WeakRefrence对象被回收时,回收的对象的引用是会被添加到该队列中的。准备完这些工作后开启线程调用ensureGone开始检测内存是否有泄露。

void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = TimeUnit.NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    this.removeWeaklyReachableReferences();
    if(!this.gone(reference) && !this.debuggerControl.isDebuggerAttached()) {
        this.gcTrigger.runGc();
        this.removeWeaklyReachableReferences();
        if(!this.gone(reference)) {
            long startDumpHeap = System.nanoTime();
            long gcDurationMs = TimeUnit.NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
            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));
        }
    }
}

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

private void removeWeaklyReachableReferences() {
    KeyedWeakReference ref;
    while((ref = (KeyedWeakReference)this.queue.poll()) != null) {
        this.retainedKeys.remove(ref.key);
    }
}

这里先调用removeWeaklyReachableReferences,这里会从ReferenceQueue中取出一个KeyedWeakReference,如果不为null说明已经回收,然后将Set中对应的key也删除即可,这个属于没有泄露的情况。当然如果为null,就继续ensureGone后面的操作,先调用依次GC,然后通过removeWeaklyReachableReferences再检查一次,类似第一次,如果回收就会从Set中移除key属于正常的情况,否则就是有内存泄露了,这次就需要通过heapDumper生成dump文件,这个文件保存了内存堆的信息。后面基于这个文件对内存泄露进行分析。

前面我们知道heapdumpListener 就是ServiceHeapDumpListener,这里我们通过它的analyze方法来分析内存泄露。这里会通过我们dump文件创建一个HeapDump对象。

public final class ServiceHeapDumpListener implements Listener {
    private final Context context;
    private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;

    public ServiceHeapDumpListener(Context context, Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
        LeakCanaryInternals.setEnabled(context, listenerServiceClass, true);
        LeakCanaryInternals.setEnabled(context, HeapAnalyzerService.class, true);
        this.listenerServiceClass = (Class)Preconditions.checkNotNull(listenerServiceClass, "listenerServiceClass");
        this.context = ((Context)Preconditions.checkNotNull(context, "context")).getApplicationContext();
    }

    public void analyze(HeapDump heapDump) {
        Preconditions.checkNotNull(heapDump, "heapDump");
        HeapAnalyzerService.runAnalysis(this.context, heapDump, this.listenerServiceClass);
    }
}

接着调用HeapAnalyzerService服务来分析,它是一个IntentService

public final class HeapAnalyzerService extends IntentService {
    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) {
        Intent intent = new Intent(context, HeapAnalyzerService.class);
        intent.putExtra("listener_class_extra", listenerServiceClass.getName());
        intent.putExtra("heapdump_extra", heapDump);
        context.startService(intent);
    }

    public HeapAnalyzerService() {
        super(HeapAnalyzerService.class.getSimpleName());
    }

    protected void onHandleIntent(Intent intent) {
        String listenerClassName = intent.getStringExtra("listener_class_extra");
        HeapDump heapDump = (HeapDump)intent.getSerializableExtra("heapdump_extra");
        ExcludedRefs androidExcludedDefault = AndroidExcludedRefs.createAndroidDefaults().build();
        HeapAnalyzer heapAnalyzer = new HeapAnalyzer(androidExcludedDefault, heapDump.excludedRefs);
        AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
        AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
    }
}

在onHandleIntent中主要是通过HeapAnalyzer来调用checkForLeak来分析的。

public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
    long analysisStartNanoTime = System.nanoTime();
    if(!heapDumpFile.exists()) {
        IllegalArgumentException snapshot1 = new IllegalArgumentException("File does not exist: " + heapDumpFile);
        return AnalysisResult.failure(snapshot1, this.since(analysisStartNanoTime));
    } else {
        ISnapshot snapshot = null;

        AnalysisResult className;
        try {
            snapshot = this.openSnapshot(heapDumpFile);
            IObject e = this.findLeakingReference(referenceKey, snapshot);
            if(e == null) {
                className = AnalysisResult.noLeak(this.since(analysisStartNanoTime));
                return className;
            }

            String className1 = e.getClazz().getName();
            AnalysisResult result = this.findLeakTrace(analysisStartNanoTime, snapshot, e, className1, true);
            if(!result.leakFound) {
                result = this.findLeakTrace(analysisStartNanoTime, snapshot, e, className1, false);
            }

            AnalysisResult var9 = result;
            return var9;
        } catch (Exception var13) {
            className = AnalysisResult.failure(var13, this.since(analysisStartNanoTime));
        } finally {
            this.cleanup(heapDumpFile, snapshot);
        }

        return className;
    }
}

checkForLeak主要用来分析dump文件,通过heapDumpFile打开一个快照对象ISnapshot,然后通过findLeakingReference查找可能存在的泄露的引用对象,然后再通过findLeakTrace为这个泄露的引用对象生成输出路径,最后通过AnalysisResult作为结果返回,最终这个结果会被显示出来。

private IObject findLeakingReference(String key, ISnapshot snapshot) throws SnapshotException {
    Collection refClasses = snapshot.getClassesByName(KeyedWeakReference.class.getName(), false);
    if(refClasses.size() != 1) {
        throw new IllegalStateException("Expecting one class for " + KeyedWeakReference.class.getName() + " in " + refClasses);
    } else {
        IClass refClass = (IClass)refClasses.iterator().next();
        int[] weakRefInstanceIds = refClass.getObjectIds();
        int[] arr$ = weakRefInstanceIds;
        int len$ = weakRefInstanceIds.length;

        for(int i$ = 0; i$ < len$; ++i$) {
            int weakRefInstanceId = arr$[i$];
            IObject weakRef = snapshot.getObject(weakRefInstanceId);
            String keyCandidate = PrettyPrinter.objectAsString((IObject)weakRef.resolveValue("key"), 100);
            if(keyCandidate.equals(key)) {
                return (IObject)weakRef.resolveValue("referent");
            }
        }

        throw new IllegalStateException("Could not find weak reference with key " + key);
    }
}

查找泄露对象的引用首先通过snapshot的getClassesByName查找名字为KeyedWeakReference的引用,然后获取到对应这个类的弱引用实例对象的id,通过id可以取到对应的弱引用对象,然后读取它内部的key值,如果和我们查找的key匹配就说明这个引用就是我们要查找的泄露对象。