在 Android 开发过程中会遇到内存问题,在内存问题的分析中有一项必不可少的环节是对 hprof 文件的分析,常见的 MAT 和 Leakcanry 都是针对hprof文件的分析工具。
本文我们来分析一下 Leakcanry 的检测原理。
弱引用: 在垃圾回收时,无论内存是否充足,都会将弱引用包装的对象回收。
当JVM进行垃圾回收时,无论内存是否充足,如果该对象只有弱引用存在,那么该对象会被垃圾回收器回收,同时该引用会被加入到关联的ReferenceQueue。因此程序可以通过判断引用队列中是否已经包含指定的引用,来了解被引用的对象是否被GC回收 (引用队列中存在指定的弱引用,说明对象被回收)。
所以 Leakcanary 在进行内存泄露的监控时,利用弱引用的上述特性,在对象生命周期结束后主动gc并检查该对象的弱引用是否被回收,如果弱引用没有被正常回收,说明在对象生命周期结束以后,该对象还被其他对象持有他的非弱引用。该对象还有到达GC ROOTS的可达路径。如果在对象生命周期结束后弱引用不存在了,说明该对象已经被JVM的垃圾回收器正常回收了,该对象的内存空间也被正常回收。
我们先从入口开始
LeakCanary
// LeakCanary.class
public static @NonNull RefWatcher install(@NonNull Application application) {
// 构建一个 AndroidRefWatcherBuilder 对象,并设置一系列参数,最终用于构建 RefWatcher 对象。
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
小结:
这里通过 Builder 模式构建一个 RefWatcher 对象,这个类就是用来监测指定对象内存是否泄漏的类。同时给这个 Builder 设置一些参数 (如:系统内存泄漏的信息 AndroidExcludedRefs、分析内存泄漏原因的 DisplayLeakService)。
下面我们来看下 buildAndInstall()
做了哪些操作。
AndroidRefWatcherBuilder
// AndroidRefWatcherBuilder.class
public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
// build 里面会做很多初始化操作。
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
if (enableDisplayLeakActivity) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
if (watchActivities) {
// 检测 Activity 的内存泄漏
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
// 检测 Fragment 的内存泄漏(包括V4包的Fragment)
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
RefWatcherBuilder
build()
方法里面会做很多对象的初始化操作,了解就行。
// RefWatcherBuilder.class
public final RefWatcher build() {
if (isDisabled()) {
return RefWatcher.DISABLED;
}
if (heapDumpBuilder.excludedRefs == null) {
heapDumpBuilder.excludedRefs(defaultExcludedRefs());
}
HeapDump.Listener heapDumpListener = this.heapDumpListener;
if (heapDumpListener == null) {
heapDumpListener = defaultHeapDumpListener();
}
DebuggerControl debuggerControl = this.debuggerControl;
if (debuggerControl == null) {
debuggerControl = defaultDebuggerControl();
}
HeapDumper heapDumper = this.heapDumper;
if (heapDumper == null) {
heapDumper = defaultHeapDumper();
}
WatchExecutor watchExecutor = this.watchExecutor;
if (watchExecutor == null) {
watchExecutor = defaultWatchExecutor();
}
GcTrigger gcTrigger = this.gcTrigger;
if (gcTrigger == null) {
gcTrigger = defaultGcTrigger();
}
if (heapDumpBuilder.reachabilityInspectorClasses == null) {
heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
}
return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
heapDumpBuilder);
}
在 LeakCanary 初始化过程中,我们知道 AndroidRefWatcherBuilder.buildAndInstall()
过程会调用 Activity监测的逻辑 ActivityRefWatcher.install(context, refWatcher)
。
ActivityRefWatcher
// ActivityRefWatcher.class
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
// 通过 Application 注册 Activity 生命周期回调接口。
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
// 在Activity 被销毁的时候去进行监听(Activity被销毁后,理应被回收,所以在这里触发监听操作)。
refWatcher.watch(activity);
}
};
小结:
这里我们只要了解 Activity 是在 onDestroyed 生命周期里被监测的就可以了,下面开始分析核心逻辑。
RefWatcher.watch()
和 RefWatcher.ensureGoneAsync()
// RefWatcher.class
private final Set<String> retainedKeys; //CopyOnWriteArraySet类型
// 引用队列,被回收的弱引用都会存入引用队列中,这是弱引用的特性。
private final ReferenceQueue<Object> queue;
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
// 1.会为每一个被观察的对象生成一个 UUID 随机变量,并保存到 retainedKeys 中。
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
// 2.将被观察的对象包装成弱引用,并与key和引用对象作关联。
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
// 3.然后执行被观察对象是否被回收的逻辑
ensureGoneAsync(watchStartNanoTime, reference);
}
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
// 注意:这里的 watchExecutor其实是 AndroidWatchExecutor,在主线程空闲时,才去异步执行 Retryable。
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
// 核心逻辑
return ensureGone(reference, watchStartNanoTime);
}
});
}
小结:
这里的 watchExecutor其实是 AndroidWatchExecutor,在主线程空闲时,才去异步执行 Retryable。
问题: 如何实现主线程空闲再去异步执行的逻辑?
利用 MessageQueue.IdleHandler 在主线程空闲时才执行的特点,在 IdleHandler 中执行异步操作。
在分析 ensureGone()
方法之前,我们先来分析一下 RefWatcher.removeWeaklyReachableReferences()
方法的作用。
RefWatcher.removeWeaklyReachableReferences()
private void removeWeaklyReachableReferences() {
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
// 将已经被回收的对象(即queue中的元素)对应的 key 从 retainedKeys 移除。
retainedKeys.remove(ref.key);
}
}
小结:
先回顾一下前面两个变量:
retainedKeys
、ReferenceQueue
。
retainedKeys:
每监测一个对象,就会生成一个 UUID 的随机变量 key,并存入 retainedKeys 中,说明监测的对象个数跟 retainedKeys 内元素的个数一样多。
ReferenceQueue:
每一个被回收的弱引用对象都会被放入 ReferenceQueue 中(这是虚拟机操作的)。
.
我们将 retainedKeys 看做是监测对象的总数,ReferenceQueue 看做是回收对象的数量,理论上如果没有内存泄漏(对retainedKeys 进行元素移除操作前),那么 retainedKeys 和 ReferenceQueue 内的元素个数应该相等,因此,可能内存泄漏的元素个数 = retainedKeys - ReferenceQueue
。
.
那么,如何表示这个减号的操作呢?使用元素移除操作remove()
即可。
RefWatcher.ensureGone()
这是核心方法
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// 1.先进行移除(可能这里已经发生过gc操作了)
removeWeaklyReachableReferences();
// Debug 模式忽略
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// 2.判断 retainedKeys 中是否存在指定的 key,gone()返回true 表示不存在。
if (gone(reference)) {
return DONE;
}
// 3.前面表示指定的弱引用没有被回收,因此手动触发GC操作。
gcTrigger.runGc(); //内部触发 Runtime.getRuntime().gc() 操作,同时让当前线程sleep(100,给gc一段处理时间)。
// 4.与步骤1相同的操作
removeWeaklyReachableReferences();
// 5.如果gone()返回false,表示可能存在内存泄漏。
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// 6.导出堆栈信息,内部其实会触发 Debug.dumpHprofData(heapDumpFile.getAbsolutePath()) 方法导出HProf文件。
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
// 7.将导出的信息封装成HeapDump对象
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
// 8.对HeapDump进行内存泄漏的分析
heapdumpListener.analyze(heapDump); // heapdumpListeners是ServiceHeapDumpListener类型
}
return DONE;
}
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
小结:
监测逻辑分为8个步骤:
- 先进行移除(可能这里已经发生过gc操作了)。
- 判断 retainedKeys 中是否存在指定的 key,gone()返回true 表示不存在。
- 前面表示指定的弱引用没有被回收,因此手动触发GC操作。
- 与步骤1相同的操作。
- 如果gone()返回false,表示可能存在内存泄漏。
- 导出堆栈信息,内部其实会触发 Debug.dumpHprofData(heapDumpFile.getAbsolutePath()) 方法导出HProf文件。
- 将导出的信息封装成HeapDump对象。
- 对HeapDump进行内存泄漏的分析。
为何 步骤 1 , 2 和 步骤 4 , 5 重复?
条件的前置判断,避免不必要的gc操作,因为在步骤1操作的时候,系统可能已经gc过了。
HeapAnalyzerService
调用链:
RefWatcher.ensureGone()
–>ServiceHeapDumpListener.analyze(heapDump)
–>HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass)
–>HeapAnalyzerService.onHandleIntentInForeground(intent)
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);
// 解析 heapDump.heapDumpFile 的文件信息
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
HeapAnalyzer
public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,
@NonNull String referenceKey, boolean computeRetainedSize) {
long analysisStartNanoTime = System.nanoTime();
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
listener.onProgressUpdate(PARSING_HEAP_DUMP);
// 1.将 heapDumpFile 生成 Snapshot。
Snapshot snapshot = parser.parse();
listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
// 2.移除里面重复的 GcRoots
deduplicateGcRoots(snapshot);
listener.onProgressUpdate(FINDING_LEAKING_REF);
// 3.查找可能泄漏的弱引用对象是否可以在snapshot中找到,如果无法,说明没有泄漏,若找到,则说明存在泄漏。
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
// False alarm, weak reference was cleared in between key check and heap dump.
if (leakingRef == null) {
String className = leakingRef.getClassObj().getClassName();
return noLeak(className, since(analysisStartNanoTime));
}
// 4.执行到这里说明监测的对象存在泄漏,因此查找该对象到GCRoot的最短引用路径(LeakTrace)。
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
// 查找制定key的
private Instance findLeakingReference(String key, Snapshot snapshot) {
// 1. 从snapshot中找到名称为 KeyedWeakReference 的 class 对象集。
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.");
}
List<String> keysFound = new ArrayList<>();
// 2.从这个对象集中获取所有 KeyedWeakReference 实例对象
for (Instance instance : refClass.getInstancesList()) {
List<ClassInstance.FieldValue> values = classInstanceValues(instance);
// 3.从KeyedWeakReference中获取key的值。
Object keyFieldValue = fieldValue(values, "key");
if (keyFieldValue == null) {
keysFound.add(null);
continue;
}
String keyCandidate = asString(keyFieldValue);
// 4.将检测对象的key值与snapshot中所有KeyedWeakReference对象的key值进行比较,若相等,则表示存在泄漏。
if (keyCandidate.equals(key)) {
return fieldValue(values, "referent");
}
keysFound.add(keyCandidate);
}
throw new IllegalStateException(
"Could not find weak reference with key " + key + " in " + keysFound);
}
小结:
checkForLeak()
的步骤:
- 将 heapDumpFile 生成 Snapshot。
- 移除里面重复的 GcRoots。
- 查找可能泄漏的弱引用对象是否可以在snapshot中找到,如果无法,说明没有泄漏,若找到,则说明存在泄漏。
- 执行到步骤4说明监测的对象存在泄漏,因此查找该对象到GCRoot的最短引用路径(LeakTrace)。
findLeakingReference()
的步骤:
- 从snapshot中找到名称为 KeyedWeakReference 的 class 对象集。
- 从这个对象集中获取所有 KeyedWeakReference 实例对象。
- 从KeyedWeakReference中获取key的值。
- 将检测对象的key值与snapshot中所有KeyedWeakReference对象的key值进行比较,若相等,则表示存在泄漏。