上篇文章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
可以看到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 extends AbstractAnalysisResultService> 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