LeakCanary简易解析

前言

本篇基于1.6.1版本源码阅读, 本篇内容就是搞懂LeakCanary如何做到内存泄漏定位的主要流程, 不抠具体细节.

正文

老样子, 我们直接从从LeakCanary.install(this)作为入口开始看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}

public RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}

通过AndroidRefWatcherBuilder对象进行一系列相关对象的初始化, 包括ServiceHeapDumpListener, ExcludedRefs,以及最重要的RefWatcher.

  1. ServiceHeapDumpListener:堆解析监听, 主要负责启动解析Heap服务以及负责处理引用路径的解析服务的连接
  2. ExcludedRefs: 内存泄漏分析的白名单
  3. RefWatcher: 观测引用是否弱可达, 通过它来观测是否该回收的内存未被回收导致内存泄漏, 如果存在这种情况, 会触发HeapDumper的记录

我们通过ActivityRefWatcher.install(context, refWatcher)查看Actvitiy的内存泄漏的分析流程

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void install(Context context, RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}

private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};

可以看到注册了一个ActivityLifecycleCallbacks, 在页面生命周期走到onDestroy的时候, 会触发refWatcher.watch(activity)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void watch(Object watchedReference) {
watch(watchedReference, "");
}

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

申明弱引用, 放入activity对象, 注册关联queue引用队列

1
2
3
4
5
6
7
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}

AndroidRefWatcherBuilder可以通过watchDelay(long delay, TimeUnit unit)设置是否延迟观测, 如果有设置, 则会在保证在主线程内延迟进行分析内存泄漏; 否则直接执行分析处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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;
}
// 重新执行gc
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
2
3
4
5
6
7
8
private void removeWeaklyReachableReferences() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}

每个activity申明弱引用的时候都会有个ID, ID保存在retainedKeys集合中, 首先遍历移除被gc回收的对象, 如果这个时候retainedKeys集合为空, 则表示不存在内存泄漏的情况. 否则手动执行GC, 再次判断移除, 这个时候如果retainedKeys内仍存在ID, 则说明有内存泄漏的情况存在.

在存在内存泄漏的情况下, 通过heapDumper.dumpHeap()获取堆内存快照, 通过heapdumpListener.analyze去进行解析.

1
2
3
4
public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}

1
2
3
4
5
6
7
8
9
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);
}

这里启动了HeapAnalyzerServiceIntentService, 这个服务主要做的就是去解析我们的.hprof文件, 主要的工作内容在onHandleIntentInForeground方法内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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);
}

然后通过haha库, 将.hprof文件解析结果AnalysisResult对象, 通过AbstractAnalysisResultService.sendResultToListener传递启动DisplayLeakService服务.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String leakInfo = leakInfo(this, heapDump, result, true);
CanaryLog.d("%s", leakInfo);

boolean resultSaved = false;
boolean shouldSaveResult = result.leakFound || result.failure != null;
if (shouldSaveResult) {
heapDump = renameHeapdump(heapDump);
resultSaved = saveResult(heapDump, result);
}

PendingIntent pendingIntent;
String contentTitle;
String contentText;

if (!shouldSaveResult) {
contentTitle = getString(R.string.leak_canary_no_leak_title);
contentText = getString(R.string.leak_canary_no_leak_text);
pendingIntent = null;
} else if (resultSaved) {
pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);

if (result.failure == null) {
if (result.retainedHeapSize == AnalysisResult.RETAINED_HEAP_SKIPPED) {
String className = classSimpleName(result.className);
if (result.excludedLeak) {
contentTitle = getString(R.string.leak_canary_leak_excluded, className);
} else {
contentTitle = getString(R.string.leak_canary_class_has_leaked, className);
}
} else {
String size = formatShortFileSize(this, result.retainedHeapSize);
String className = classSimpleName(result.className);
if (result.excludedLeak) {
contentTitle = getString(R.string.leak_canary_leak_excluded_retaining, className, size);
} else {
contentTitle =
getString(R.string.leak_canary_class_has_leaked_retaining, className, size);
}
}
} else {
contentTitle = getString(R.string.leak_canary_analysis_failed);
}
contentText = getString(R.string.leak_canary_notification_message);
} else {
contentTitle = getString(R.string.leak_canary_could_not_save_title);
contentText = getString(R.string.leak_canary_could_not_save_text);
pendingIntent = null;
}
// New notification id every second.
int notificationId = (int) (SystemClock.uptimeMillis() / 1000);
showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
afterDefaultHandling(heapDump, result, leakInfo);
}

将对应的.hprof文件重命名, 对应泄漏的内存对象关联key, 传递到DisplayLeakActivity做显示, 另外进行通知显示.

总结

通过上文的简易解析, 我们可以得出LeakCanary的一个大概的原理流程.通过application注册ActivityLifecycleCallbacks的回调, 在每个activity销毁的时候, 将activity的弱引用包装绑定在ReferenceQueue上, 当GC的时候, 可以通过queue移除已被回收的activity对象key, 获得始终未被回收的对象, 判断为是内存泄漏, 根据haha库解析heap dumps,获取引用路径最终在DisplayLeakActivity上显示我们熟悉的内存泄漏的列表内容.