这里首先将LeakCannary的原理写在这里,让大家有一个初步的印象,然后我们再一步步去分析具体的实现逻辑
LeakCannary原理
1. Activity onDestroy之后将它放到一个WeakReference
2. 这个WeakReference关联到一个ReferenceQueue
3. 查看ReferenceQueue是否存在 Activity的引用
4. 如果该Acitivty泄漏了,Dump出内存信息,再去分析泄漏路径
首先我们先回顾一下Java的内存模型中的四种引用方式
根据上一篇中提到的LeakCannary的使用方法我们知道,LeakCanary.install()是使用LeakCannary的一个前提,那我们就从这里开始入手分析,看LeakCannary如何一步步分析定位内存泄漏的。
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
该方法的返回值是一个RefWatcher,主要是通过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;
}
第二行有一个非空判断,LeakCanaryInternals.installedRefWatcher不能被重复创建,buildAndInstall()这个方法只能被调用一次,watchActivities和watchFragments的默认值都是true,表示会对Activity和Fragment进行监测,我们以Activity为例,跟进去看一看具体逻辑。
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
可以看到上面这个方法,首先获取了Application,然后注册了activity的生命周期监听,这个callback如下
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
可见是在activity ondestroy的时候,调用refWatcher监测这个activity。
首先我们对RefWatcher类中的几个主要成员变量做个简单介绍
private final WatchExecutor watchExecutor; //用于执行内存泄漏监测用的
private final DebuggerControl debuggerControl; //查询是否正在调试中,如果正在调试中,LeakCannary是不会进行内存泄漏检测的
private final GcTrigger gcTrigger; //进行GC的一些处理
private final HeapDumper heapDumper; //dump内存泄漏文件
private final HeapDump.Listener heapdumpListener;
private final HeapDump.Builder heapDumpBuilder;
private final Set<String> retainedKeys; //待检测的和产生内存泄漏的引用的key
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();
String key = UUID.randomUUID().toString();//对reference添加一个为一点key值
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
将传入的watchedReference创建了一个我们所需要的弱引用,最后调用ensureGoneAsync开启异步线程,来对是否有内存泄漏进行分析。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
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;
}
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);
这个方法其实主要是确保activity是否已经被回收了
watchDurationMs表示调用该方法到产生垃圾回收所用的时间
removeWeaklyReachableReferences()我们看一下,它的主要作用是,检查一下已经到达弱引用队列的对象,然后retainedKeys集合中对应的key移除掉,从而保证,retainedKeys剩余的key对应的引用还没有被回收。
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);
}
}
我们接着回到上面的ensureGone方法,第5行和第9行,分别判断了是否正在调试,或者是否所有的reference都已经被回收,然后返回对应的值。
下面第12行调用了gcTrigger.runGc(),这里其实是手动调用了一次GC,为的是保证在进行后面的分析检测之前,GC已经执行过,并将可以被回收的对象进行了回收。然后下面紧接着,再一次调用了removeWeaklyReachableReferences()清除已经到达引用队列的弱引用。再下面经过if(gone())判断之后,就可以认为此时还存在于retainedKeys的key对应的引用,即是产生内存泄漏的对象。然后就开始调用analyze()方法来对内存泄漏进行分析。
这里我们先简单总结一下,LeakCannary做的几件事情:
1. 首先会创建一个refWatcher,启动一个ActivityRefWatcher
2. tongguo ActivityLifecycleCallbacks把Activity的onDestroy生命周期关联
3. 最后在线程池中去开始分析我们的泄漏
analyze()是一个接口,它的实现在一个ServiceHeapDumpListener里面
@Override
public void analyze(@NonNull HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
我们跟进HeapAnalyzerService.runAnalysis()来看一下
public final class HeapAnalyzerService extends ForegroundService
implements AnalyzerProgressListener {
...
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);
}
...
HeapAnalyzerService本质是一个IntentService,所以runAnalysis会回调到onHandleIntent,跟代码进去,发现经过封装会到onHandleIntentInForeground()这里。
@Override protected void onHandleIntent(@Nullable Intent intent) {
onHandleIntentInForeground(intent);
}
protected abstract void onHandleIntentInForeground(@Nullable Intent intent);
@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);
// checkForLeak很重要,用于进行分析
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
// 将分析结果进行显示
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
checkForLeak就是进一步分析内存,将前面创建好的hprof文件解析成Snapshot对象分析。
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);
Snapshot snapshot = parser.parse();
listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
deduplicateGcRoots(snapshot);
listener.onProgressUpdate(FINDING_LEAKING_REF);
//根据referenceKey来查询结果中是否有我们需要的对象
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
// False alarm, weak reference was cleared in between key check and heap dump.
//对上面的结果进行判断,为空表示不存在
if (leakingRef == null) {
return noLeak(since(analysisStartNanoTime));
}
//不为空,则把路径找出来
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
总结checkForLeak:
1. 把.hprof转为Snapshot
2. 优化GCRoots
3. 找出泄漏的对象,找出泄漏对象的最短路径
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.");
}
List<String> keysFound = new ArrayList<>();
for (Instance instance : refClass.getInstancesList()) {
List<ClassInstance.FieldValue> values = classInstanceValues(instance);
Object keyFieldValue = fieldValue(values, "key");
if (keyFieldValue == null) {
keysFound.add(null);
continue;
}
String keyCandidate = asString(keyFieldValue);
if (keyCandidate.equals(key)) {
return 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) {
listener.onProgressUpdate(FINDING_SHORTEST_PATH);
ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
//查找泄漏的路径
ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);
// False alarm, no strong reference path to GC Roots.
if (result.leakingNode == null) {
return noLeak(since(analysisStartNanoTime));
}
listener.onProgressUpdate(BUILDING_LEAK_TRACE);
LeakTrace leakTrace = buildLeakTrace(result.leakingNode);
String className = leakingRef.getClassObj().getClassName();
long retainedSize;
if (computeRetainedSize) {
listener.onProgressUpdate(COMPUTING_DOMINATORS);
// Side effect: computes retained size.
snapshot.computeDominators();
Instance leakingInstance = result.leakingNode.instance;
//计算泄漏的大小
retainedSize = leakingInstance.getTotalRetainedSize();
// TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
if (SDK_INT <= N_MR1) {
listener.onProgressUpdate(COMPUTING_BITMAP_SIZE);
retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
}
} else {
retainedSize = AnalysisResult.RETAINED_HEAP_SKIPPED;
}
return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
since(analysisStartNanoTime));
}
总结LeakCannary原理
1. Activity onDestroy之后将它放到一个WeakReference
2. 这个WeakReference关联到一个ReferenceQueue
3. 查看ReferenceQueue是否存在 Activity的引用
4. 如果该Acitivty泄漏了,Dump出内存信息,再去分析泄漏路径