LeakCanary原理解析

上篇文章Leakcanary的使用演示了LeakCanary的使用,这篇文章分析下,LeakCanary(基于1.6.1版本)的原理。在使用LeakCanary时,需要在自定义的Application中,调用LeakCanary.install(this);下面看看LeakCanary类的install方法的具体实现:

#LeakCanary.java
public final class LeakCanary {
    public static RefWatcher install(Application application) {
        return ((AndroidRefWatcherBuilder)refWatcher(application)// 创建AndroidRefWatcherBuilder对象
	.listenerServiceClass(DisplayLeakService.class)//配置监听结果的DisplayLeakService
	.excludedRefs(AndroidExcludedRefs.createAppDefaults().build()))// 配置排除的系统泄露
	.buildAndInstall();//返回一个RefWatcher对象,使用这个对象监听Activity的引用
    }
    ...
}

下面看看AndroidRefWatcherBuilder类的buildAndInstall()方法的具体实现:

#AndroidRefWatcherBuilder.java
public final class AndroidRefWatcherBuilder extends RefWatcherBuilder {
    private static final long DEFAULT_WATCH_DELAY_MILLIS;
    private final Context context;
    private boolean watchActivities = true;
    private boolean watchFragments = true;

    ...

    public RefWatcher buildAndInstall() {
        if (LeakCanaryInternals.installedRefWatcher != null) {
            throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
        } else {
            RefWatcher refWatcher = this.build();
            if (refWatcher != RefWatcher.DISABLED) {
                if (this.watchActivities) {
		    //关键代码,这里是监听Activity的引用
                    ActivityRefWatcher.install(this.context, refWatcher);
                }

                if (this.watchFragments) {
		    //关键代码,这里是监听Fragment的引用
                    Helper.install(this.context, refWatcher);
                }
            }

            LeakCanaryInternals.installedRefWatcher = refWatcher;
            return refWatcher;
        }
    }
   ...
}

下面看看监听Activity的引用的逻辑,ActivityRefWatcher类的install方法的具体实现:

#ActivityRefWatcher.java
public final class ActivityRefWatcher {
    private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() {
        public void onActivityDestroyed(Activity activity) {
	    //关键代码,这里通过监听到Activity的onDestroy方法使,调用RefWatcher的watch方法来监听该Activity是否泄漏
            ActivityRefWatcher.this.refWatcher.watch(activity);
        }
    };
    ...
    public static void install(Context context, RefWatcher refWatcher) {
        Application application = (Application)context.getApplicationContext();
        ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
	//监听Activity的生命周期
        application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
    }
    ...
}

这个方法内部,通过Application的registerActivityLifecycleCallbacks方法来监听Activity的生命周期,当Activity调用onDestroy方法时,调用RefWatcher类的watch方法来检测该Activity是否内存泄漏。下面看看RefWatcher类的watch方法的具体实现:

#RefWatcher.java
public final class RefWatcher {
    public static final RefWatcher DISABLED = (new RefWatcherBuilder()).build();
    // 线程控制器,在 onDestroy() 之后并且主线程空闲时执行内存泄漏检测
    private final WatchExecutor watchExecutor;

    // 判断是否处于调试模式,调试模式中不会进行内存泄漏检测,因为在调试过程中可能会保留上一个引用从而导致错误信息上报。
    private final DebuggerControl debuggerControl;

    // 用于主动触发GC操作
    private final GcTrigger gcTrigger;

    // 堆信息转储者,dump 内存泄漏处的 heap 信息到 hprof 文件
    private final HeapDumper heapDumper;

    private final Listener heapdumpListener;
    private final Builder heapDumpBuilder;

    // 保存每个被检测对象所对应的唯一key
    private final Set retainedKeys;

    // 引用队列,和WeakReference配合使用,当弱引用所引用的对象被GC回收,该弱引用就会被加入到这个队列
    private final ReferenceQueue queue;

    ...

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

    public void watch(Object watchedReference, String referenceName) {
        if (this != DISABLED) {
            Preconditions.checkNotNull(watchedReference, "watchedReference");
            Preconditions.checkNotNull(referenceName, "referenceName");
            long watchStartNanoTime = System.nanoTime();
	    // 为被检测对象生成唯一的key值,并保存到retainedKeys
            String key = UUID.randomUUID().toString();
            this.retainedKeys.add(key);

	    // 创建被检测对象的弱引用,并传入该对象的key
            KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, this.queue);

	    //异步检测这个对象是否被回收
            this.ensureGoneAsync(watchStartNanoTime, reference);
        }
    }
}
 
  

可以看到watch方法内部调用其重载方法,这个重载方法内部,主要做了如下几件事:
1.为被检测的对象生成一个唯一的key值,并保存到retainedKeys这个Set集合中。
2.创建一个弱引用对象,并和一个引用队列进行关联。KeyedWeakReference是继承自WeakReference,对KeyedWeakReference进行了一层包装。
3.调用ensureGoneAsync方法异步检测这个对象是否被回收

下面继续看ensureGoneAsync方法的具体实现:

#RefWatcher.java
public final class RefWatcher { 
    ...
    private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
        this.watchExecutor.execute(new Retryable() {
            public Result run() {
	        //关键代码,调用了RefWatcher类的ensureGone方法进行检测
                return RefWatcher.this.ensureGone(reference, watchStartNanoTime);
            }
        });
    }
    ...
}

这个方法内部调用了this.watchExecutor的execute方法,这个this.watchExecutor是AndroidWatchExecutor类型的,通过 Handler来实现的,而且用的是 IdleHandler。用的是postRunnable,用来在 app 空闲的时候执行。
这样run方法就得到执行,后续调用RefWatcher类的ensureGone方法进行检测,下面看看RefWatcher类的ensureGone方法:

#RefWatcher.java
public final class RefWatcher { 
    ...
      Result ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
        long gcStartNanoTime = System.nanoTime();
        long watchDurationMs = TimeUnit.NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
	//关键代码1   移除对象已经被回收的弱引用
        this.removeWeaklyReachableReferences();
        
        if (this.debuggerControl.isDebuggerAttached()) { //关键代码2  调试模式检测不准确  
            return Result.RETRY;
        } else if (this.gone(reference)) {  //关键代码3 判断引用是否存在,不存在,表示被对象被回收
            return Result.DONE;
        } else {
	    //关键代码4	 触发GC
            this.gcTrigger.runGc();

	    //关键代码5    移除对象已经被回收的弱引用
            this.removeWeaklyReachableReferences();

            if (!this.gone(reference)) {  //关键代码6	如果该引用还存在,就表示对象已经泄露
                long startDumpHeap = System.nanoTime();
                long gcDurationMs = TimeUnit.NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
		//关键代码7	 dump出heap的内存快照
                File heapDumpFile = this.heapDumper.dumpHeap();
                if (heapDumpFile == HeapDumper.RETRY_LATER) {
                    return Result.RETRY;
                }

                long heapDumpDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
		//关键代码8	构建HeapDump对象
                HeapDump heapDump = this.heapDumpBuilder.heapDumpFile(heapDumpFile)
		                                        .referenceKey(reference.key)
							.referenceName(reference.name)
							.watchDurationMs(watchDurationMs)
							.gcDurationMs(gcDurationMs)
							.heapDumpDurationMs(heapDumpDurationMs)
							.build();
                //关键代码9	分析HeapDump对象
		this.heapdumpListener.analyze(heapDump);
            }

            return Result.DONE;
        }
    }

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

    private void removeWeaklyReachableReferences() {
        KeyedWeakReference ref;
        // 当弱引用所引用的对象被回收,就会把该引用放到queue中,所以可以通过queue来判断对象是否被回收
        while ((ref = (KeyedWeakReference) queue.poll()) != null) {
            retainedKeys.remove(ref.key);
        }
    }
    
    ...

}

这个方法内部,在关键代码1处,先移除已经被回收的对象的弱引用对象,接着在关键代码2处,会判断是否是调试模式,如果是调试模式,会因为检测不准确,直接返回一个RETRY常量,接着在关键代码3处,会判断引用是否存在,如果不存在,则表示对象已经被回收,如果引用还存在,在关键代码4处,手动触发一次GC,接着在关键代码5处,再次移除已回收对象的弱引用对象,在关键代码6处,再次判读弱引用对象是否仍然存在,如果仍然存在,则表示发生了内存泄漏。在关键代码7处,调用this.heapDumper.dumpHeap()方法,这里的this.heapDumper其实是AndroidHeapDumper,所以具体执行的的dumpHeap方法是AndroidHeapDumper类的dumpHeap方法,这个方法中,主要是通过Debug.dumpHprofData(heapDumpFile.getAbsolutePath());来完成的。具体的实现,可以去看看AndroidHeapDumper类的dumpHeap方法的具体实现,这里不展开分析了。在关键代码8处,构建了一个DumpHeap对象,在关键代码9处,通过heapdumpListener的analyze方法去分析这dumpHeap对象。这个heapdumpListener其实是ServiceHeapDumpListener类型的,所以代码跳转到ServiceHeapDumpListener类中

#ServiceHeapDumpListener.java

public final class ServiceHeapDumpListener implements Listener {
    ...

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

这个方法内部调用了HeapAnalyzerService.runAnalysis方法,下面看看这个方法的具体实现:

#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 listenerServiceClass) {
        LeakCanaryInternals.setEnabledBlocking(context, HeapAnalyzerService.class, true);
        LeakCanaryInternals.setEnabledBlocking(context, listenerServiceClass, true);
        Intent intent = new Intent(context, HeapAnalyzerService.class);
        intent.putExtra("listener_class_extra", listenerServiceClass.getName());
        intent.putExtra("heapdump_extra", heapDump);
	//关键代码1  启动HeapAnalyzerService,这个HeapAnalyzerService是在一个单独的进程中,读者可以查看LeakCanary的manifest文件
        ContextCompat.startForegroundService(context, intent);
    }

    ...

    protected void onHandleIntentInForeground(@Nullable Intent intent) {
        if (intent == null) {
            CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.", new Object[0]);
        } else {
            String listenerClassName = intent.getStringExtra("listener_class_extra");
            HeapDump heapDump = (HeapDump)intent.getSerializableExtra("heapdump_extra");
            HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
            //关键代码2     分析内存泄露的地方
	    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey, heapDump.computeRetainedHeapSize);
            //关键代码3     发送内存泄露检测结果的通知
	    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
        }
    }

    ...
}

在调用了runAnalysis方法后,会启动这个HeapAnalyzerService(HeapAnalyzerService是IntentService的子类,
由于分析是个很耗时的过程,并且也很耗内存,为了减少对检测的app的影响,这里将HeapAnalyzerService工作在一个新的进程中,关于进程的名称,读者可以看看LeakCanary的manifest的配置文件),HeapAnalyzerService服务启动后,onHandleIntentInForeground方法就会执行。在关键代码2处,会通过HeapAnalyzer的checkForLeak方法来分析内存泄漏的地方。

#HeapAnalyzer.java
public final class HeapAnalyzer {
    public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey, boolean computeRetainedSize) {
        long analysisStartNanoTime = System.nanoTime();
        if (!heapDumpFile.exists()) {
            Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
            return AnalysisResult.failure(exception, this.since(analysisStartNanoTime));
        } else {
            try {
                this.listener.onProgressUpdate(Step.READING_HEAP_DUMP_FILE);

		// 使用haha库解析.hprof文件
                HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
                HprofParser parser = new HprofParser(buffer);
                this.listener.onProgressUpdate(Step.PARSING_HEAP_DUMP);

		// 解析.hprof文件生成对应的快照对象
                Snapshot snapshot = parser.parse();
                this.listener.onProgressUpdate(Step.DEDUPLICATING_GC_ROOTS);

		// 删除gcRoots中重复的根对象RootObj
                this.deduplicateGcRoots(snapshot);
                this.listener.onProgressUpdate(Step.FINDING_LEAKING_REF);

		// 检查对象是否泄露
                Instance leakingRef = this.findLeakingReference(referenceKey, snapshot);
                // 如果没有泄漏则调用AnalysisResult.noLeak,如果寻找泄漏则调用this.findLeakTrace()方法 查找引用链
                return leakingRef == null ? AnalysisResult.noLeak(this.since(analysisStartNanoTime)) : this.findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
            } catch (Throwable var10) {
                return AnalysisResult.failure(var10, this.since(analysisStartNanoTime));
            }
        }
    }

   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.");
        } else {
            List keysFound = new ArrayList();
            Iterator var5 = refClass.getInstancesList().iterator();

            while(var5.hasNext()) {
                Instance instance = (Instance)var5.next();
                List values = HahaHelper.classInstanceValues(instance);
                Object keyFieldValue = HahaHelper.fieldValue(values, "key");
                if (keyFieldValue == null) {
                    keysFound.add((Object)null);
                } else {
                    String keyCandidate = HahaHelper.asString(keyFieldValue);
                    if (keyCandidate.equals(key)) {
                        return (Instance)HahaHelper.fieldValue(values, "referent");
                    }

                    keysFound.add(keyCandidate);
                }
            }

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

    private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot, Instance leakingRef, boolean computeRetainedSize) {
        this.listener.onProgressUpdate(Step.FINDING_SHORTEST_PATH);
	// 查找到GC Roots的最短引用路径
        ShortestPathFinder pathFinder = new ShortestPathFinder(this.excludedRefs);
        Result result = pathFinder.findPath(snapshot, leakingRef);
        if (result.leakingNode == null) {
            return AnalysisResult.noLeak(this.since(analysisStartNanoTime));
        } else {
            this.listener.onProgressUpdate(Step.BUILDING_LEAK_TRACE);

	    // 构建泄露的引用链
            LeakTrace leakTrace = this.buildLeakTrace(result.leakingNode);

            String className = leakingRef.getClassObj().getClassName();
            long retainedSize;
            if (computeRetainedSize) {
                this.listener.onProgressUpdate(Step.COMPUTING_DOMINATORS);

		// // 计算内存泄露的大小
                snapshot.computeDominators();

                Instance leakingInstance = result.leakingNode.instance;
                retainedSize = leakingInstance.getTotalRetainedSize();
                if (VERSION.SDK_INT <= 25) {
                    this.listener.onProgressUpdate(Step.COMPUTING_BITMAP_SIZE);
                    retainedSize += this.computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
                }
            } else {
                retainedSize = -1L;
            }

            return AnalysisResult.leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize, this.since(analysisStartNanoTime));
        }
    }
}

HeapAnalyzer的checkForLeak方法主要做了一下几件事情:
1.使用haha库解析.hprof文件
2.解析.hprof文件生成对应的快照对象
3.删除gcRoots中重复的根对象RootObj
4.调用findLeakingReference方法检查对象是否泄露
5.如果没有泄漏则调用AnalysisResult.noLeak,如果寻找泄漏则调用this.findLeakTrace()方法 查找引用链

查找到泄漏的引用链后,通过DisplayLeakService展示结果:

public class DisplayLeakService extends AbstractAnalysisResultService {
    public DisplayLeakService() {
    }

    protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
        String leakInfo = LeakCanary.leakInfo(this, heapDump, result, true);
        CanaryLog.d("%s", new Object[]{leakInfo});
        boolean resultSaved = false;
        boolean shouldSaveResult = result.leakFound || result.failure != null;
        if (shouldSaveResult) {
            heapDump = this.renameHeapdump(heapDump);
            resultSaved = this.saveResult(heapDump, result);
        }

        PendingIntent pendingIntent;
        String contentTitle;
        String contentText;
        ...

        int notificationId = (int)(SystemClock.uptimeMillis() / 1000L);
	//关键代码  在通知栏显示一个通知
        LeakCanaryInternals.showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
        this.afterDefaultHandling(heapDump, result, leakInfo);
    }
   ...
}

在DisplayLeakService类的onHeapAnalyzed方法中,构建通知懒得 标题,内容等信息,然后在通知栏显示这个通知。

总结:
1.RefWatcher.watch()创建一个 KeyedWeakReference到要被监控的对象。
2.然后在后台线程检查引用是否被清除,如果没有,调用GC。
3.如果引用还是未被清除,把heap内存dump到APP对应的文件系统中的一个 .hprof文件中。
4.在另一个进程中的 HeapAnalyzerService有一个 HeapAnalyzer使用HAHA解析这个文件。
5.在Heap Dump中, HeapAnalyzer根据唯一的reference key找到了 KeyedWeakReference,并定位了泄漏的引用。
6.HeapAnalyzer计算到 GCRoots的最短强引用路径,并确定是否泄漏,如果是的话,建立导致泄漏的引用链。
7.引用链传递到APP进程中的 DisplayLeakService,并以通知的形式展示出来。

8.LeakCanary对于内存泄漏的检测非常有效,但也并不是所有的内存泄漏都能检测出来。无法检测出Service中的内存泄漏问题,如果要检查Service,则需要在Service的onDestroy方法中使用RefWatcher的watch()方法来监控。

9.在Android 4.0以前,由于是不存在ActivityLifecycleCallbacks接口的,若要在4.0之前使用就需要在基类 BaseActivity的onDestroy()方法中调用RefWatcher的watch()方法来监控。

10.检查泄漏的核心原理是利用了ReferenceQueue,当一个对象被gc掉的时候通知用户线程,进行额外的处理时,就需要使用引用队列了。ReferenceQueue即这样的一个对象,当一个obj被gc掉之后,其相应的包装类,即ref对象会被放入queue中。我们可以从queue中获取到相应的对象信息,同时进行额外的处理。比如反向操作,数据清理等。

参考:

性能优化工具(九)-LeakCanary
https://www.jianshu.com/p/70b8c87ea877

LeakCanary原理分析
https://cloud.tencent.com/developer/article/1401257

Android 源码系列之从源码的角度深入理解LeakCanary的内存泄露检测机制(中)
https://blog.csdn.net/llew2011/article/details/52958563

Android 源码系列之从源码的角度深入理解LeakCanary的内存泄露检测机制(下)
https://blog.csdn.net/llew2011/article/details/52958567

LeakCanary 源码分析
https://www.jianshu.com/p/18b04ff44c5f

你可能感兴趣的:(android)