1 LeakCanary简介
LeakCanary 是 Square 公司的一个开源库。通过它可以在 App 运行过程中检测内存泄漏,当内存泄漏发生时会生成发生泄漏对象的引用链,并在手机中弹出通知栏告知程序开发人员。
LeakCanary 两大核心
- 检测内存泄漏
- 分析内存泄漏对象的引用链
LeakCanary 中对内存泄漏检测的核心原理就是基于 WeakReference 和 ReferenceQueue 实现的。
WeakReference
Java 中的 WeakReference 是弱引用类型,每当发生 GC 时,它所持有的对象如果没有被其他强引用所持有,那么它所引用的对象就会被回收。
ReferenceQueue
WeakReference 的构造函数可以传入 ReferenceQueue,当 WeakReference 指向的对象被垃圾回收器回收时,会把 WeakReference 放入 ReferenceQueue 中。
2 LeakCanary 中对内存泄漏检测的核心原理
-
当一个 Activity 需要被回收时,就将其包装到一个 WeakReference 中,并且在 WeakReference 的构造器中传入自定义的 ReferenceQueue
-
给包装后的 WeakReference 做生成唯一标记 Key,并且在一个强引用 Set 中添加相应的 Key 记录
-
主动触发 GC,遍历自定义 ReferenceQueue 中所有的记录,并根据获取的 Reference 对象将 Set 中的记录也删除
-
经过上面 3 步之后,还保留在 Set 中的就是:应当被 GC 回收,但是实际还保留在内存中的对象,也就是发生泄漏了的对象。
-
然后结合dump memory的 hpof 文件, 用 haha开源库 分析出内存泄漏的对象的引用链
3 LeakCanary 源码分析
提示:分析的 LeakCanary 版本为:1.5.4
我们知道一个可回收对象在 System.gc() 之后就应该被 GC 回收,但是 Android 系统何时回收 Activity 是未知的。一般我们认为,当 Activity 调用 onDestroy 方法时就说明这个 Activity 就已经处于无用状态了,可以被 GC 了。所以就需要监听每个 Activity 的 onDestroy 方法的调用。
我们在 Application 中初始化 LeakCanary 代码如下
public class MyApplication extends Application {
private RefWatcher refWatcher;
public static RefWatcher getRefWatcher(Context context) {
MyApplication application = (MyApplication) context.getApplicationContext();
return application.refWatcher;
}
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
refWatcher = LeakCanary.install(this);
}
}
从上面代码可以看出,LeakCanary 会首先判断当前进程是否是用于分析heap内存的而创建的进程,不是才进行LeakCanary的安装操作LeakCanary.install 方法返回的是 RefWatcher 对象,用于监测实例对象的引用状态。LeakCanary只检测了 Activity 的内存泄漏,若要检测 Fragment 的内存泄漏,则可以在 Fragment onDestroy() 方法中加入如下代码:
MyApplication.getGetRefWatcher(activity).watch(this)
我们看 LeakCanary.install(application)
方法
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
此方法主要是来构造 RefWatcher 对象,refWatcher 方法构造了一个 AndroidRefWatcherBuilder 对象,AndroidRefWatcherBuilder的构造方法中将传入的 context 转为 ApplicationContext,在接着传入一个 DisplayLeakService 的Class对象,它的作用是展示泄露分析的结果日志,然后会展示一个用于跳转到显示泄露界面DisplayLeakActivity的通知。在调用 buildAndInstall 方法前调用了 excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
方法,这个方法主要是指定了一系列需要忽略的场景,这些特殊的场景都被枚举在 AndroidExcludedRefs 中。因为有些特殊机型的系统本身就存在一些内存泄漏的情况,导致 Activity 不被回收,所以在检测内存泄漏时,需要将这些情况排除在外。继续看 AndroidRefWatcherBuilder 类的buildAndInstall()
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}
需要监听每个 Activity 的 onDestroy 方法在 ActivityRefWatcher.install
方法中,主要是通过注册 Android 系统提供的 ActivityLifecycleCallbacks,来监听 Activity 的生命周期方法的调用,代码如下:
public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).watchActivities();
}
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override public void onActivityStarted(Activity activity) {
}
@Override public void onActivityResumed(Activity activity) {
}
@Override public void onActivityPaused(Activity activity) {
}
@Override public void onActivityStopped(Activity activity) {
}
@Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
可以看出当监听到 Activity 的 onDestroy 方法后,会将其传给 RefWatcher 的 watch 方法。RefWatcher是 LeakCanary 的一个核心类,用来检测一个对象是否会发生内存泄漏。主要实现是在 watch 方法中
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
public void watch(Object watchedReference, String referenceName) {
// 省略部分无关代码...
// 生成一个随机的字符串 key,这个 key 就是用来标识 WeakReference 的,就相当于给 WeakReference 打了个标签
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
// 将被检测对象包装到一个 WeakReference 中,并用上面代码生成的 key 标识
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
// 开始执行检测操作
ensureGoneAsync(watchStartNanoTime, reference);
}
注释中已经做了相关代码说明,此处直接看检测泄漏的方法ensureGoneAsync
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
内存泄漏的检测与分析是比较消耗性能的,因此为了尽量不影响 UI 线程的渲染,在 ensureGoneAsync 方法中调用了 WatchExecutor 的 execute 方法来执行检测操作。
@Override public void execute(Retryable retryable) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
postWaitForIdle(retryable, 0);
}
}
void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
mainHandler.post(new Runnable() {
@Override public void run() {
waitForIdle(retryable, failedAttempts);
}
});
}
void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
如果是主线程,则直接调用 waitForIdle 方法,否则用 Handler post 一个 Runnable 到主线程中执行 waitForIdle 方法;waitForIdle 方法中向主线程 MessageQueue 中插入了一个 IdleHandler,IdleHandler 只会在主线程空闲时才会被 Looper 从队列中取出并执行,能够有效避免内存检测工作阻塞 UI 渲染。
通过调用 addIdleHandler 方法也经常用来做 App 的启动优化,比如在 Application 的 onCreate 方法中经常做 3 方库的初始化工作。可以将优先级较低、暂时使用不到的 3 方库的初始化操作放到 IdleHandler 中,从而加快 Application 的启动过程。
通过 WatchExecutor 执行了一个重载的方法 ensureGone
@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
// 省略部分无关代码...
// 遍历 ReferenceQueue 中所有的元素,并根据每个元素中的 key,相应的将集合 retainedKeys 中的元素也删除
removeWeaklyReachableReferences();
// 规避调试模式
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// 检测是否已经回收
if (gone(reference)) {
return DONE;
}
// 如果没有被回收则手动执行GC
gcTrigger.runGc();
// 再次检测并移除已被回收的 references
removeWeaklyReachableReferences();
// 判断集合 retainedKeys 是否还包含被检测对象的弱引用,包含则说明被检测对象并发生了内存泄漏
if (!gone(reference)) {
// 还没有被回收,则dump堆信息
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);
// 调起分析进程进行分析
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
我们先来看下removeWeaklyReachableReferences
方法
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);
}
}
这个方法从 retainedKeys 中移除已经被回收的 WeakReference 的标志,继续看下gone(reference)
方法
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
这个方法用来判断 reference 是否被回收,在 retainedKeys 中包含此reference.key ,则说明 WeakReference 引用的对象没有被回收,也就是发生了内存泄漏。
4 总结
- LeakCanary 中对内存泄漏检测的核心原理就是基于 WeakReference 和 ReferenceQueue 实现的
- LeakCanary 中监听 Activity 生命周期是 ActivityRefWatch 类通过注册 Android 系统提供的 ActivityLifecycleCallbacks来实现
- LeakCanary 主要检测内存泄漏的核心类:RefWatcher
- 通过向主线程 MessageQueue 中插入 IdleHandler 方式来优化内存泄漏的检测时机
- 在 LeakCanary 的初始化方法 install 中,通过 excludedRefs 方法统一规避特殊机型的内存泄漏
网友评论