LeakCanary源码
官方解释
在一个Activity执行完onDestroy()之后,将它放入WeakReference中,然后将这个WeakReference类型的Activity对象与ReferenceQueque关联。这时再从ReferenceQueque中查看是否有没有该对象,如果没有,执行gc,再次查看,还是没有的话则判断发生内存泄露了。最后用HAHA这个开源库去分析dump之后的heap内存
基本使用
(1) 添加app.gradle依赖 :
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
// Optional, if you use support library fragments:
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
(2)初始化LeakCanary,在Application中添加:
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
// 判断当前进程是否为LeakCanary进程,该进程运行一个HeapAnalyzerService服务
// 如果不是,则初始化LeakCanary进程
if (! LeakCanary.isInAnalyzerProcess(this)) {
LeakCanary.install(this);
}
// Normal app init code...
}
}
注意:除了activities和fragments外,LeakCanary支持监听应用中的任何对象,假如这个对象不再使用到的话,通过执行下列代码实现对某个对象的监听。代码如下:
RefWatcher.watch(myDetachedView)
源码详解
LeakCanary的入口方法是LeanCanary$install方法,该方法源码如下:
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \LeanCanary.java
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application) // 创建一个AndroidRefWatcherBuilder对象
.listenerServiceClass(DisplayLeakService.class) // 注释1
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) // 注释2
.buildAndInstall(); // 注释2
}
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \LeanCanary.java
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
return new AndroidRefWatcherBuilder(context);
}
1234567891011121314
install方法目的就是创建并返回一个RefWatcher对象,这个RefWatcher是LeakCanary的核心类,通过建造者模式构建。其中,listenerServiceClass方法传入了展示分析结果的Service(DisplayLeakService);excludedRefs方法排除开发中可以忽略的泄漏路径;buildAndInstall是主要函数,实现对activity的释放监听。接下来,我们直接看buildAndInstall方法源码:
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// AndroidRefWatcherBuilder.java
public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should
+ "only be called once.");
}
// 构建一个RefWacher对象
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
if (enableDisplayLeakActivity) {
LeakCanaryInternals.setEnabledAsync(context,
DisplayLeakActivity.class, true);
}
// 监听所有Activities
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
// 监听所有fragments
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
1234567891011121314151617181920212223242526
从上述源码可知,它首先会调用AndroidRefWatcherBuilder的build方法构建一个RefWatcher实例,然后分别调用ActivityRefWatcher.install方法和FragmentRefWatcher.Helper.install方法实现对所有activities和fragments的释放监听。
下面我们就以分析如何监听activity为例:
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// ActivityRefWatcher.java
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
// 获取应用的application
Application application = (Application) context.getApplicationContext();
// 实例化一个ActivityRefWatcher对象
ActivityRefWatcher activityRefWatcher =
new ActivityRefWatcher(application, refWatcher);
// 调用registerActivityLifecycleCallbacks来监听Activity的生命周期
application.registerActivityLifecycleCallbacks(activityRefWatcher.
lifecycleCallbacks);
}
123456789101112
从install方法源码可以看出,LeakCanary主要通过调用Application的registerActivityLifecycleCallbacks方法实现对activity释放(销毁)监听,该方法主要用来统一管理所有activity的生命周期。所有Activity在销毁时都会回调ActivityLifecycleCallbacks的onActivityDestroyed方法,也就是说,LeakCanary是在Activity的onDestory方法中实施监听的,通过调用RefWatcher.watch方法实现。源码如下:
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// ActivityRefWatcher.java
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
// 在Activity销毁时,监控当前activity
// 传入的是activity的引用
refWatcher.watch(activity);
}
};
接下来,我们来看LeakCanary是如何监听activity是否发生泄漏。RefWatcher.watch源码:
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \RefWatcher.java
public void watch(Object watchedReference) {
// watchedReference为被监视的activity引用
watch(watchedReference, "");
}
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
// 自动生成一个主键,作为全局唯一标识符
// 并插入到retainedKeys集合中
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
// 1. 将activity的引用包装到KeyedWeakReference中
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
// 2. 检测是否发生泄漏
ensureGoneAsync(watchStartNanoTime, reference);
}
123456789101112131415161718192021222324
在RefWatcher.watch方法中,完成以下两件事情:
首先,将当前被监控的activity引用、自动生成的key和一个ReferenceQueue 对象包装到一个KeyedWeakReference对象中,该对象继承于WeakReference(弱引用)。监测机制利用了Java的WeakReference和ReferenceQueue,通过将Activity包装到WeakReference中,被WeakReference包装过的Activity对象如果能够被回收,则说明引用可达,垃圾回收器就会将该WeakReference引用(包含被监控的activity)放到ReferenceQueue中,通过监测ReferenceQueue里面的内容就能检查到Activity是否能够被回收。KeyedWeakReference类源码如下:
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \KeyedWeakReference.java
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
private final ReferenceQueue<Object> queue;
final class KeyedWeakReference extends WeakReference<Object> {
public final String key;
public final String name;
KeyedWeakReference(Object referent, String key, String name,
ReferenceQueue<Object> referenceQueue) {
super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue,
"referenceQueue"));
this.key = checkNotNull(key, "key");
this.name = checkNotNull(name, "name");
}
}
123456789101112131415161718
其次,检测当前被监控的activity是否发生了泄漏,通过调用RefWatcher#ensureGoneAsync方法实现,该方法又调用了RefWatcher#ensureGone。相关源码如下:
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \RefWatcher.java
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// 1. 确定是否存在内存泄漏
// (1)判断当前activity的引用是否存在ReferenceQueue中,
// 如果存在,则说明引用可达,能够被GC回收,同时将其key从retainedKeys集合中删除
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// (2)确定retainedKeys集合中是否存在该activity对应的key
// 如果不存在了,说明该对象已经被回收,直接返回
if (gone(reference)) {
return DONE;
}
// (3)如果存在,先触发一下GC操作,再尝试判断该activity的对象引用
// 是否保存到了ReferenceQueue
gcTrigger.runGc();
removeWeaklyReachableReferences();
// 2. 再次确定retainedKeys集合中是否存在该activity对应的key
// 如果仍然存在,则说明发生了内存泄漏.生成堆内存快照,分析快照
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// (1) 创建heapDump文件,还没写入
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime()
- startDumpHeap);
//(2)创建HeapDump对象
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile)
.referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
//(3)调用heapdumpListener分析
// 调用HeapAnalyzerService的analyze实现,即后台执行分析任务
heapdumpListener.analyze(heapDump);
}
return DONE;
}
从ensureGone方法源码可知,它首先会去确定被监控的activity对象是否发生了内存泄漏,然后如果确定了确实发生了泄漏,就会dump内存快照并对快照进行分析,最终得到泄漏的具体信息。接下来,我们将对这三个方面进行详细分析:
(1)确定是否存在内存泄漏
确定被监控的activity对象是否存在内存泄漏,主要是通过调用removeWeaklyReachableReferences、gone方法实现的,具体策略为:首先,调用removeWeaklyReachableReferences判断当前被监控的activity对象的引用是否存在ReferenceQueue中,如果存在说明,该activity对象引用可达,能够被GC回收,此时就将其key从retainedKeys这个Set集合中移除;然后,调用gone方法确定当前被监控的activity对象的引用是否存在retainedKeys集合,如果不存在,说明该activity对象已经被回收,直接返回。但是,如果仍然存在,为了确定GC延迟或误判,手动触发一下GC操作,然后再进行一次上面的判定操作,如果Gone方法仍然返回false,则说明被监控的activity对象发生了内存泄漏。相关源码如下:
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \RefWatcher.java
private boolean gone(KeyedWeakReference reference) {
// 判定被监控的activity对象对应的key
// 是否存在于retainedKeys集合中
// 如果不存在,说明该对象已经被GC回收,不存在内存泄漏
return !retainedKeys.contains(reference.key);
}
private void removeWeaklyReachableReferences() {
KeyedWeakReference ref;
// 判定被监控的activity引用是否保存在ReferenceQueue中
// 如果存在,则将其对应的key从retainedKeys集合中移除
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
2)将堆内存转储到文件并分析,获取泄漏对象的GC最短强引用路径
前面说到,如果Gone方法返回false说明被监控的activity对象发生了内存泄漏,接下来,将进入内存泄漏分析过程。具体策略为:首先,创建一个heapDump文件,此时还没有写入;然后,将堆内存信息转储到该heapDump文件,并创建一个HeapDump对象;最后,调用heapdumpListener的analyze方法进入分析流程。需要注意的是,heapdumpListener由RefWatch构造方法传入,前面说到RefWatch对象是通过建造者模式的形式创建的,因此,我们找到了AndroidRefWatcherBuilder,该类包含一个defaultHeapDumpListener方法即可说明heapdumpListener(类型为HeapDump.Listener)的实例化过程,即实现类为ServiceHeapDumpListener。也就是说,heapdumpListener.analyze为调用ServiceHeapDumpListener#analyze方法,该方法中最终调用的是HeapAnalyzerService#runAnalysis方法在后台服务中执行堆快照分析任务。相关源码如下:
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// \AndroidRefWatcherBuilder.java
@Override protected @NonNull HeapDump.Listener defaultHeapDumpListener() {
return new ServiceHeapDumpListener(context, DisplayLeakService.class);
}
// \leakcanary-1.6.3\leakcanary-android\src\main\java\com\squareup\leakcanary
// ServiceHeapDumpListener.java
public final class ServiceHeapDumpListener implements HeapDump.Listener {
private final Context context;
private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;
public ServiceHeapDumpListener(@NonNull final Context context,
@NonNull final Class<? extends
AbstractAnalysisResultService> listenerServiceClass) {
this.listenerServiceClass = checkNotNull(listenerServiceClass,
"listenerServiceClass");
this.context = checkNotNull(context, "context").getApplicationContext();
}
@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);
// listenerServiceClass
// 负责记录日志和展示通知
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
// 启动自身,并将其置为前台
ContextCompat.startForegroundService(context, intent);
}
}
12345678910111213141516
从HeapAnalyzerService源码可知,它是一个IntentServcie,实际上就是一个Service,只是与普通Service不同的是,它被启动后会在子线程执行具体的任务,即调用onHandleIntentInForeground方法执行任务,当任务执行完毕后,该Service会被自动销毁。在HeapAnalyzerService#runAnalysis方法中,就是启动该IntentService,并将其置为前台服务,以降低被系统杀死的概率。现在,我们就看下HeapAnalyzerService#onHandleIntentInForeground方法做了什么,源码如下:
protected void onHandleIntentInForeground(@Nullable Intent intent) {
// DisplayLeakService.class
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
// 创建HeapAnalyzer
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer(heapDump.excludedRefs, this,
heapDump.reachabilityInspectorClasses);
// HeapAnanlyzer工具分析
// 即分析堆内存快照,找出 GC roots 的最短强引用路径,并确定是否是泄露
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile,
heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
// 启动DisplayLeakService记录日志和展示通知
AbstractAnalysisResultService.sendResultToListener(this,
listenerClassName,
heapDump, result);
}
从该方法源码可知,它主要是创建一个HeapAnalyzer对象,并调用该对象的checkForLeak进行分析,然后将得到的结果交给DisplayLeakService进行通知展示。这里,我们分析下HeapAnalyzer#checkForLeak方法分析堆内存快照文件的流程,该方法源码如下:
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);
// 将heap文件封装成MemoryMappedFileBuffer
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
// 创建hprof解析器,解析hprof文件
HprofParser parser = new HprofParser(buffer);
listener.onProgressUpdate(PARSING_HEAP_DUMP);
Snapshot snapshot = parser.parse();
listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
// 移除相同GC root
deduplicateGcRoots(snapshot);
listener.onProgressUpdate(FINDING_LEAKING_REF);
// 找出泄漏的对象
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));
}
//根据leakingRef寻找引用路径
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef,
computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
由上述源码可知,该方法最终调用findLeakingReference方法来判断是否真的存在内存泄漏,如果存在(leakingRef!=null),就调用findLeakTrace方法找出这个泄漏对象的GC Root最短强引用路径。
总结
- LeakCanary是通过在Application的registerActivityLifecycleCallbacks方法实现对Activity销毁监听的,该方法主要用来统一管理所有activity的生命周期。
- 所有Activity在销毁时在其OnDestory方法中都会回调
ActivityLifecycleCallbacks#onActivityDestroyed方法,而LeakCanary要做的就是在该方法中调用RefWatcher#watch方法实现对activity进行内存泄漏监控。 - 那么,LeakCanary是如何判断某个Activity可能会发生内存泄漏呢?答案是:WeakReference和ReferenceQueue,即LeakCanary利用了Java的WeakReference和ReferenceQueue,通过将Activity包装到WeakReference中,被WeakReference包装过的Activity对象如果能够被回收,则说明引用可达,垃圾回收器就会将该WeakReference引用存放到ReferenceQueue中。假如我们要监视某个activity对象,LeakCanary就会去ReferenceQueue找这个对象的引用,如果找到了,说明该对象是引用可达的,能被GC回收,如果没有找到,说明该对象有可能发生了内存泄漏。
- 最后,LeakCanary会将Java堆转储到一个.hprof文件中,再使用Shark(堆分析工具)分析.hprof文件并定位堆转储中“滞留”的对象,并对每个"滞留"的对象找出 GC roots 的最短
强引用路径,并确定是否是泄露,如果泄漏,建立导致泄露的引用链。