1. 内存泄露的定义
-
传统定义:
- 申请的内存忘记释放了。
-
Android(或 JVM)的内存泄露:
- 短生命周期的对象被长生命周期的对象持有,导致短生命周期的对象不能被垃圾回收器释放。
2. 垃圾回收机制
Java 和其他语言采用不同的垃圾回收策略,主要包括两种:
2.1 引用计数法
-
应用语言:
- Python、Objective-C、Swift 等
-
原理:
- 用一个计数器记录对象被引用的次数,当引用计数降到 0 时,该对象被认为是垃圾对象。
-
缺点:
- 存在循环引用问题,可能导致垃圾对象无法被回收。
2.2 可达性分析法
-
应用语言:
- Java(JVM)
-
原理:
- JVM 从一组被称为 GC Roots 的对象出发,向下搜索所有可达对象。
- 如果一个对象能被 GC Roots 引用到,则认为它不是垃圾;否则,即使对象间互相引用,也会被视为垃圾。
-
GC Roots 包括:
- 线程栈中的局部变量(正在调用的方法中的参数和局部变量)
- 存活的线程对象
- JNI 的引用
- Class 对象(因为 Android 加载的 Class 通常不会卸载)
- 静态变量中引用的对象
3. 内存泄露问题的影响
- 内存泄露不会立刻导致程序崩溃,但随着应用使用,被长生命周期对象持有的短生命周期对象无法回收,导致可用内存逐渐减少。
- 当内存耗尽时,可能在应用的任何位置抛出
OutOfMemoryError
,而每次错误堆栈可能都不同,增加问题排查难度。
4. LeakCanary 监听内存泄漏的整体流程和核心原理概要
LeakCanary的粗略流程就如下图所示:
1. 初始化和注册
-
Application 注册
在 Application 的
onCreate()
方法中,先判断当前进程是否为 LeakCanary 分析进程,避免在该进程中初始化应用逻辑,然后调用LeakCanary.install(this)
。kotlinpublic class MyApp extends Application { @Override public void onCreate() { super.onCreate(); if (LeakCanary.isInAnalyzerProcess(this)) { return; } LeakCanary.install(this); } }
-
构建 RefWatcher
LeakCanary.install()
内部会调用refWatcher()
方法创建一个AndroidRefWatcherBuilder
,并设置用于接收内存泄漏分析结果的 listener(例如 DisplayLeakService),以及排除 Android SDK 或手机厂商修改引入的已知泄漏对象(excludedRefs)。 -
注册生命周期回调
在
buildAndInstall()
中,根据配置:- 注册 Activity 生命周期回调(通过
ActivityRefWatcher
),在 Activity 的onDestroyed()
时调用refWatcher.watch(activity)
进行监控。 - 注册 Fragment 生命周期回调(通过
FragmentRefWatcher.Helper
),在 Fragment 销毁时同样监控相关对象。
- 注册 Activity 生命周期回调(通过
2. 监控对象及弱引用机制
-
监控目标
LeakCanary 关注的对象通常是那些生命周期结束后应被回收的对象,如 Activity、Fragment、Service 或其他大对象。
-
弱引用实现
- 当调用
RefWatcher.watch(watchedReference, referenceName)
时,LeakCanary 为被观察对象创建一个自定义的弱引用KeyedWeakReference
(继承自WeakReference
),同时生成一个唯一的 key,并将这个 key 添加到内部的"怀疑名单"(retainedKeys)。 - 此弱引用关联了一个
ReferenceQueue
。当垃圾回收器回收目标对象后,该弱引用会被加入队列,从而可以通过轮询队列确认该对象是否真的被回收。
- 当调用
-
检测回收情况
- 在异步任务中(由 WatchExecutor 执行),首先调用
removeWeaklyReachableReferences()
从队列中移除已经回收对象对应的 key。 - 如果
retainedKeys
中仍包含目标对象的 key,说明对象没有被回收,即可能存在内存泄漏。
- 在异步任务中(由 WatchExecutor 执行),首先调用
3. 触发垃圾回收与堆转储
-
主动触发 GC
如果检测到被观察对象依然存在(即弱引用未被清除),LeakCanary 会主动调用
gcTrigger.runGc()
触发一次垃圾回收,再次清理弱引用。 -
堆转储(Heap Dump)
- 如果在再次检测后目标对象依然存在,则认为该对象未能正常释放,LeakCanary 开始执行堆转储,通过调用
heapDumper.dumpHeap()
获取 hprof 文件。 - 为避免系统干扰,还会显示一个通知,并通过 Toast 等机制确认堆转储时机。
- 如果在再次检测后目标对象依然存在,则认为该对象未能正常释放,LeakCanary 开始执行堆转储,通过调用
4. 堆文件分析与泄漏确认
-
堆分析服务
堆转储完成后,LeakCanary 会将转储文件、目标对象的 key 以及其他监控信息封装成一个
HeapDump
对象,并将其传递给监听器服务(如 DisplayLeakService)。 -
独立进程分析
在独立进程中,
HeapAnalyzer
会加载 hprof 文件,对整个内存快照进行解析:- 查找
KeyedWeakReference
实例,验证目标对象是否真的存在。 - 分析从 GC Roots 到该对象的引用路径,生成泄漏链(Leak Trace)。
- 结合分析耗时等信息构建最终的
AnalysisResult
。
- 查找
-
结果展示
分析结果最终通过
AbstractAnalysisResultService.sendResultToListener()
发送到指定的监听服务,开发者可以在 LeakCanary 提供的 UI 中查看详细的泄漏信息,也可以将结果上报服务器。
5. 总结
LeakCanary 的内存泄漏检测原理可以归纳为以下几点:
- 注册与监控
通过在 Application 中初始化,注册 Activity 和 Fragment 生命周期回调,在对象销毁时调用watch()
监控待回收对象。 - 弱引用与引用队列
使用自定义的弱引用(KeyedWeakReference)与 ReferenceQueue 来检测对象是否已被垃圾回收,通过 retainedKeys 记录未回收的对象标识。 - 主动 GC 与堆转储
当检测到对象未被回收时,主动触发垃圾回收、转储堆内存数据,并启动独立进程进行详细分析。 - 泄漏路径分析
通过解析堆转储文件,生成对象的引用路径图,帮助定位导致内存泄漏的原因。
5. LeakCanary 原理详细解析
5.1 引用类型
在开始之前,我们需要先做一些前置知识的准备,了解四种引用类型。
-
强引用:
- 对象只要被强引用,就不会被垃圾回收。
-
弱引用:
- 可以通过
get()
方法获得引用的对象,若对象被垃圾回收,则返回null
。 - 在对象被回收前,弱引用会被放入关联的队列中,可用来判断对象是否被回收。
- 可以通过
-
软引用:
- 类似弱引用,但在内存不足时才会被回收。
-
虚引用:
- 不能通过
get()
获得对象,主要用于跟踪对象回收的状态。
- 不能通过
5.2 监控原理
LeakCanary 是一个用于检测内存泄露的工具,由于2.0以上的版本对其内容做了改动,为了方便我们理解原理,于是选取了1.6.3的版本,其内部的基础是:
- LeakCanary 通过注册 Application 和 Activity 的生命周期回调,在 Activity 和 Fragment 销毁时开始观察其内存状态。
- 1.x 版本的 LeakCanary 主要监控 support 包和 API 26 以上的 Fragment。
源码流程分析:
1. 完成LeakCanary的注册:
kotlin
public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
// 判断是否在主进程中
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
// 使用LeakCanary
LeakCanary.install(this);
}
}
2. 进入LeakCanary#install(Application application)
install方法内部完成了整个LeakCanary的初始化,RefWatcher 字面意思就是引用观察者,
listenerServiceClass()
方法:允许指定一个自定义的 Service 类(继承自 AbstractAnalysisResultService),用于接收内存泄漏分析结果并发送通知。excludedRefs
:无法处理的内存泄漏,例如三方SDK的泄漏,AndroidExcludedRefs里边包含了一些AndroidSDK自带的内存泄漏,还有手机厂商修改带来的额外的内存泄漏buildAndInstall()
: 创建监察者。
kotlin
/**
* Creates a {@link RefWatcher} that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class) // DisplayLeakService 可以自定义,可以把分析结果上送服务器。
// 无法处理的内存泄漏,例如三方SDK的泄漏,AndroidExcludedRefs里边包含了一些AndroidSDK自带的内存泄漏,还有手机产商修改带来的额外的内存泄漏
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
// 创建一个用于观察引用的对象
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
return new AndroidRefWatcherBuilder(context);
}
// 手机厂商修改导致的内存泄漏
import static com.squareup.leakcanary.internal.LeakCanaryInternals.HUAWEI;
import static com.squareup.leakcanary.internal.LeakCanaryInternals.LENOVO;
import static com.squareup.leakcanary.internal.LeakCanaryInternals.LG;
import static com.squareup.leakcanary.internal.LeakCanaryInternals.MEIZU;
import static com.squareup.leakcanary.internal.LeakCanaryInternals.MOTOROLA;
import static com.squareup.leakcanary.internal.LeakCanaryInternals.NVIDIA;
import static com.squareup.leakcanary.internal.LeakCanaryInternals.SAMSUNG;
import static com.squareup.leakcanary.internal.LeakCanaryInternals.VIVO;
3. 深入AndroidRefWatcherBuilder#buildAndInstall()
-
单例限制:首先检查是否已有安装的 RefWatcher,确保只在当前进程中调用一次。
-
构建 RefWatcher:调用内部 build() 方法构建 RefWatcher 实例。
-
监控配置:
- 如果不是 DISABLED(即未在 release 模式下禁用检测),则根据配置决定是否启用显示泄漏的 Activity(DisplayLeakActivity)。
- 根据 watchActivities 和 watchFragments 配置,分别安装 Activity 和 Fragment 的生命周期监控器。
-
保存实例:将构建好的 RefWatcher 保存到全局变量中,确保后续统一使用。
-
返回实例:返回构建好的 RefWatcher,后续可以用于定制其他监控需求。
kotlin
/**
* 创建一个 RefWatcher 实例,并将该实例保存在 LeakCanary 内部,
* 以便后续通过 LeakCanary.installedRefWatcher() 访问到该实例。
* 同时,如果配置了监控 Activity 和 Fragment,则会自动注册生命周期回调,
* 在 Activity 或 Fragment 销毁时开始监控它们是否会被正确回收。
*
* @throws UnsupportedOperationException 如果在同一进程中多次调用此方法(只允许初始化一次)
*/
public @NonNull RefWatcher buildAndInstall() {
// 检查是否已经初始化过 RefWatcher,如果已经安装则抛出异常,防止重复初始化
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
// 调用内部 build() 方法构造 RefWatcher 实例
RefWatcher refWatcher = build();
// 如果构造的 RefWatcher 不是 DISABLED(即当前不是 release 版的空实现)
if (refWatcher != DISABLED) {
// 如果启用了显示泄漏分析 Activity 的功能(默认开启),则异步设置使得
// 分析结果对应的 Activity 图标可以在应用列表中显示出来
if (enableDisplayLeakActivity) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
// 如果配置为监控 Activity,则安装 ActivityRefWatcher,
// 通过注册 Application 的生命周期回调在 Activity 销毁时开始监控
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
// 如果配置为监控 Fragment,则安装 FragmentRefWatcher,
// 通过注册 Activity 生命周期回调,在 Activity 创建时安装 Fragment 监控器
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
// 将构造好的 RefWatcher 保存在全局变量中,保证在当前进程中唯一
LeakCanaryInternals.installedRefWatcher = refWatcher;
// 返回 RefWatcher 对象,这个对象后续还可以用于定制监控其他对象(例如大对象、Service等)
return refWatcher;
}
/**
* 设置一个自定义的 {@link AbstractAnalysisResultService} 用于接收内存泄漏分析结果。
* 该方法将覆盖之前对 {@link #heapDumpListener(HeapDump.Listener)} 的设置。
*
* @param listenerServiceClass 用于监听堆转储分析结果并发送通知的 Service 类,
* 该类必须继承自 AbstractAnalysisResultService。
* @return 当前 AndroidRefWatcherBuilder 对象,支持链式调用以进一步定制 LeakCanary 配置。
*/
public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
@NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
// 检查传入的 listenerServiceClass 是否为 DisplayLeakService 或其子类,
// 如果是,则启用显示泄漏分析 Activity(即使分析结果的图标能够在 Launcher 中显示)
enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
// 创建一个新的 ServiceHeapDumpListener 实例,该 listener 用于在堆转储分析完成后接收结果,
// 并调用 heapDumpListener() 将其设置到当前构建器中
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
- ActivityRefWatcher: 在 Activity 被销毁后(调用 onDestroyed 后),自动将该 Activity 对象交给 RefWatcher 进行内存泄漏检测。具体作用如下:
-
注册生命周期回调
- 通过调用
install()
方法,将一个包含自定义生命周期回调(这里继承自 ActivityLifecycleCallbacksAdapter)的对象注册到 Application 上。 - 在这个回调中,重点关注
onActivityDestroyed(Activity activity)
方法,当某个 Activity 销毁时,自动调用refWatcher.watch(activity)
。
- 通过调用
-
自动检测内存泄漏
- 当 Activity 被销毁后,RefWatcher 会对该 Activity 进行监控,判断其是否能被正常回收,避免出现内存泄漏情况。
-
便捷的安装和卸载
- 提供
watchActivities()
和stopWatchingActivities()
方法,方便在运行时动态开启或停止对 Activity 生命周期的监控,确保不会重复注册回调。
- 提供
kotlin
public final class ActivityRefWatcher {
public static void installOnIcsPlus(@NonNull Application application,
@NonNull RefWatcher refWatcher) {
install(application, refWatcher);
}
// 下边的代码想做的事情就是对已经调用了onDestroyed的Activity进行监视
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
// application通过传入Activity的lifecycleCallbacks对象,就能够实现对所有Activity生命周期方法调用的监控,
// 并且方法中还能够获取到Activity的对象,但是没有给Service添加这样的方法。
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
// 当某一个Activity的onDestroyed被调用的时候,就会触发这里。
@Override public void onActivityDestroyed(Activity activity) {
// 对已经调用了onDestroyed进行监视。
refWatcher.watch(activity);
}
};
private final Application application;
private final RefWatcher refWatcher;
private ActivityRefWatcher(Application application, RefWatcher refWatcher) {
this.application = application;
this.refWatcher = refWatcher;
}
public void watchActivities() {
// Make sure you don't get installed twice.
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
public void stopWatchingActivities() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
}
}
- FragmentRefWatcher :LeakCanary 利用这部分代码自动注册 Fragment 生命周期监听器。通过在 Activity 创建时,安装针对 Fragment 的监控器,可以在 Fragment 销毁时(或其 View 销毁时)调用
refWatcher.watch()
,从而检测这些对象是否在合适时机被垃圾回收。
-
针对不同类型的 Fragment:
- 对于 Android O 及以上的原生 Fragment,使用
AndroidOFragmentRefWatcher
。 - 对于 Support 包中的 Fragment,尝试通过反射加载并使用
SupportFragmentRefWatcher
(前提是项目中包含了对应依赖,不过现在的项目应该很少存在Support包了)。
- 对于 Android O 及以上的原生 Fragment,使用
kotlin
public interface FragmentRefWatcher {
/**
* 注册当前 Activity 中 Fragment 的生命周期回调,
* 以便在 Fragment 的关键生命周期事件发生时进行内存泄漏检测。
*
* @param activity 当前要监控的 Activity
*/
void watchFragments(Activity activity);
final class Helper {
// SupportFragmentRefWatcher 类名,用于检测是否引入了 LeakCanary 的支持包
private static final String SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME =
"com.squareup.leakcanary.internal.SupportFragmentRefWatcher";
/**
* 安装 FragmentRefWatcher 的辅助类,
* 根据当前环境和依赖情况,动态构造适用于不同 Fragment 类型的监控器,
* 并注册 Activity 生命周期回调,在 Activity 创建时为其内部的 Fragment 注册监控。
*
* @param context 应用上下文
* @param refWatcher 用于监控对象是否被回收的 RefWatcher 实例
*/
public static void install(Context context, RefWatcher refWatcher) {
// 用于存储构造出的 Fragment 监控器
List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
// (1)原生 Fragment:Android 26(Oreo)及以上的系统支持对原生 Fragment 进行监控
// 注意:在 Android 26 之前,自带的 Fragment 不支持内存泄漏检测,
// 同时 AndroidX 包下的 Fragment 同样不支持 LeakCanary 的自动监控。
if (SDK_INT >= O) {
fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
}
try {
// (2)Support Fragment:尝试通过反射加载 SupportFragmentRefWatcher 类,
// 前提是项目引入了 com.squareup.leakcanary:leakcanary-support-fragment 依赖。
Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
Constructor<?> constructor =
fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
// 创建针对 Support 包下 Fragment 的监控器实例
FragmentRefWatcher supportFragmentRefWatcher =
(FragmentRefWatcher) constructor.newInstance(refWatcher);
fragmentRefWatchers.add(supportFragmentRefWatcher);
} catch (Exception ignored) {
// 如果反射失败(例如依赖不存在),则忽略异常
}
// 如果没有任何 Fragment 监控器可用,则直接返回,不做安装
if (fragmentRefWatchers.size() == 0) {
return;
}
// 构造 Helper 实例,将创建好的 FragmentRefWatcher 列表传入
Helper helper = new Helper(fragmentRefWatchers);
// 获取全局 Application 实例,用于注册 Activity 生命周期回调
Application application = (Application) context.getApplicationContext();
// 注册一个 Activity 生命周期回调监听器,
// 在 Activity 创建时,为该 Activity 内所有 Fragment 注册监控
application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
}
// 定义 Activity 生命周期回调,用于在 Activity 创建时为其中的 Fragment 安装监控器
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
// 当 Activity 创建时,遍历所有已构造的 Fragment 监控器,
// 并调用它们的 watchFragments 方法为当前 Activity 注册 Fragment 生命周期监听器
for (FragmentRefWatcher watcher : fragmentRefWatchers) {
watcher.watchFragments(activity);
}
}
};
// 存储所有构造的 FragmentRefWatcher 实例
private final List<FragmentRefWatcher> fragmentRefWatchers;
private Helper(List<FragmentRefWatcher> fragmentRefWatchers) {
this.fragmentRefWatchers = fragmentRefWatchers;
}
}
}
/**
* 针对 Android O 及以上版本原生 Fragment 的内存泄漏监控实现。
* 监控内容包括:Fragment 自身和它的 View 在销毁时是否被正确回收。
*/
class AndroidOFragmentRefWatcher implements FragmentRefWatcher {
private final RefWatcher refWatcher;
AndroidOFragmentRefWatcher(RefWatcher refWatcher) {
this.refWatcher = refWatcher;
}
// 定义 Fragment 生命周期回调,关注两个关键事件:
// 1. Fragment 的 View 被销毁(onFragmentViewDestroyed)
// 2. Fragment 实例被销毁(onFragmentDestroyed)
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
new FragmentManager.FragmentLifecycleCallbacks() {
@Override
public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
// 获取当前 Fragment 的 View 对象
View view = fragment.getView();
if (view != null) {
// 当 Fragment 的 View 销毁时,调用 RefWatcher 监控该 View,
// 以便检测该 View 是否最终能被垃圾回收,防止内存泄漏
refWatcher.watch(view);
}
}
@Override
public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
// 当 Fragment 本身被销毁时,同样调用 RefWatcher 监控该 Fragment 对象
refWatcher.watch(fragment);
}
};
/**
* 注册当前 Activity 中原生 Fragment 的生命周期回调,
* 这样当 Activity 中的 Fragment 的关键生命周期事件发生时(例如销毁),
* 就会自动调用相应回调进行内存泄漏检测。
*
* @param activity 当前需要监控 Fragment 的 Activity
*/
@Override
public void watchFragments(Activity activity) {
// 获取当前 Activity 的 FragmentManager
FragmentManager fragmentManager = activity.getFragmentManager();
// 注册 FragmentLifecycleCallbacks 回调,第二个参数 true 表示递归注册(监控嵌套的 Fragment)
fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
}
4. RefWatcher
内存泄漏检测核心类的分析
(1)内存泄漏的监察与捕捉:
关键点说明:
-
ReferenceQueue 的作用
当被监控的对象(通过
KeyedWeakReference
包装)仅由弱引用持有时,一旦对象被垃圾回收,该弱引用会自动入队,从而通过removeWeaklyReachableReferences()
方法将其 key 从怀疑名单中移除。 -
retainedKeys 集合
用于保存所有待检测的对象的唯一标识(key)。如果某个 key 长时间未被移除,则表示对应对象没有被回收,可能存在内存泄漏。
-
日志的分析与导出都在这个服务中处理,该服务运行在独立进程中:
js
<service
android:name="com.squareup.leakcanary.internal.HeapAnalyzerService"
android:enabled="false"
android:process=":leakcanary" />
- ensureGone 方法
核心检测逻辑:在一定时间内检查对象是否已被回收;如果未被回收,主动触发 GC,最终导出堆转储文件以进一步分析泄漏原因。
kotlin
public final class RefWatcher {
// 当 RefWatcher 被禁用时,使用一个空实现
public static final RefWatcher DISABLED = new RefWatcherBuilder<>().build();
// 任务执行器,用于异步执行检测任务
private final WatchExecutor watchExecutor;
// 用于检测调试器是否已连接,调试器可能会影响 GC 行为
private final DebuggerControl debuggerControl;
// 触发垃圾回收的工具
private final GcTrigger gcTrigger;
// 用于导出堆信息(hprof文件)的工具
private final HeapDumper heapDumper;
// 用于接收堆转储后分析结果的监听器
private final HeapDump.Listener heapdumpListener;
// 用于构建 HeapDump 对象(封装堆转储相关信息)的构造器
private final HeapDump.Builder heapDumpBuilder;
// 怀疑对象集合,用来存储待检测(可能泄漏)的对象的唯一标识(key)
private final Set<String> retainedKeys;
// 弱引用关联的队列,当被观察对象被回收后,其对应的弱引用会自动加入此队列
private final ReferenceQueue<Object> queue;
// 构造函数,初始化所有必需的组件和内部集合
RefWatcher(WatchExecutor watchExecutor, DebuggerControl debuggerControl, GcTrigger gcTrigger,
HeapDumper heapDumper, HeapDump.Listener heapdumpListener, HeapDump.Builder heapDumpBuilder) {
this.watchExecutor = checkNotNull(watchExecutor, "watchExecutor");
this.debuggerControl = checkNotNull(debuggerControl, "debuggerControl");
this.gcTrigger = checkNotNull(gcTrigger, "gcTrigger");
this.heapDumper = checkNotNull(heapDumper, "heapDumper");
this.heapdumpListener = checkNotNull(heapdumpListener, "heapdumpListener");
this.heapDumpBuilder = heapDumpBuilder;
retainedKeys = new CopyOnWriteArraySet<>();
// 创建一个 ReferenceQueue,用于跟踪被观察对象的弱引用是否被加入队列(即对象是否被回收)
queue = new ReferenceQueue<>();
}
/**
* 简化接口:使用空字符串作为引用名称
*/
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
/**
* 监控指定对象,观察它是否能够被 GC 回收。该方法会在Activity的OnDestroy 或者是Fragment的OnDestroyView 或者是Fragment的OnDestroy 执行后开始执行,
* 该方法为非阻塞,实际检查任务由 watchExecutor 异步执行。
*
* @param referenceName 用于标识被观察对象的逻辑名称
*/
public void watch(Object watchedReference, String referenceName) {
// 如果当前 RefWatcher 已经被禁用,则直接返回,release包就会返回。
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
// 记录监控开始时间(单位:纳秒),用于计算观察时长
final long watchStartNanoTime = System.nanoTime();
// 为被观察对象生成一个唯一标识 key
String key = UUID.randomUUID().toString();
// 将该 key 加入待观察的集合中,表示该对象应该在未来被回收
retainedKeys.add(key);
// 创建一个 KeyedWeakReference,将被观察对象包装为弱引用,并关联到 ReferenceQueue
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
// 异步执行检测任务,确保对象能够被垃圾回收
ensureGoneAsync(watchStartNanoTime, reference);
}
/**
* 清除所有已监控的对象,停止监控
*/
public void clearWatchedReferences() {
retainedKeys.clear();
}
// 检查所有已监控对象是否已被回收后,判断待观察集合是否为空
boolean isEmpty() {
removeWeaklyReachableReferences();
return retainedKeys.isEmpty();
}
HeapDump.Builder getHeapDumpBuilder() {
return heapDumpBuilder;
}
Set<String> getRetainedKeys() {
return new HashSet<>(retainedKeys);
}
// 异步检测:通过 watchExecutor 异步执行确保对象已被回收的检测任务
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
// 在子线程中检测对象是否已被回收
return ensureGone(reference, watchStartNanoTime);
}
});
}
// watchExecutor的具体实现:
// 以下是 watchExecutor 的执行逻辑,根据当前线程是否为主线程选择不同的等待方式
@Override public void execute(@NonNull Retryable retryable) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
// 如果在主线程,则等待主线程空闲后执行
waitForIdle(retryable, 0);
} else {
// 否则直接在后台线程执行
postWaitForIdle(retryable, 0);
}
}
/**
* 核心检测逻辑:确保被观察对象已经被垃圾回收
* 1. 记录 GC 开始时间,并计算观察对象的存活时长
* 2. 轮询 ReferenceQueue 移除已回收的引用(更新 retainedKeys)
* 3. 如果对象仍未被回收,则主动触发 GC,再次检测
* 4. 如果对象仍未被回收,则导出堆信息,并启动堆分析
*/
@SuppressWarnings("ReferenceEquality") // 显式地检查引用相等性
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
// 记录触发垃圾回收检测的时间点
long gcStartNanoTime = System.nanoTime();
// 计算从开始监控到触发 GC 的时长(毫秒)
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// 移除已被回收的对象对应的 key(从 ReferenceQueue 中轮询)
removeWeaklyReachableReferences();
// 如果调试器附加在进程上,可能会导致虚假泄漏,直接返回重试状态
if (debuggerControl.isDebuggerAttached()) {
return RETRY;
}
// 如果该对象已被回收(对应 key 已被移除),则返回 DONE 表示检测结束
if (gone(reference)) {
return DONE;
}
// 主动触发一次垃圾回收
gcTrigger.runGc();
// 再次清除 ReferenceQueue 中的已回收引用
removeWeaklyReachableReferences();
// 如果对象依然存在(未被回收),说明可能存在内存泄漏
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
// 计算 GC 触发后至开始堆转储的耗时(毫秒)
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// 导出当前堆的快照到文件
File heapDumpFile = heapDumper.dumpHeap();
// 如果无法导出堆文件(例如权限问题),返回 RETRY 状态
if (heapDumpFile == RETRY_LATER) {
return RETRY;
}
// 计算堆转储耗时(毫秒)
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
// 构建一个 HeapDump 对象,包含堆文件、观察对象的 key、名称以及各个阶段的耗时信息
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
// 通过监听器启动堆分析服务,进一步分析内存泄漏
heapdumpListener.analyze(heapDump);
// 分析服务内部的处理(例如:HeapAnalyzerService.runAnalysis)
@Override public void analyze(@NonNull HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
}
// 对象已经被回收或者完成检测,返回 DONE
return DONE;
}
// 判断目标对象是否已被回收:如果 retainedKeys 中不包含该对象的 key,则认为已被回收
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
// 不断的从队列中取出对象,引用的观察对象的弱引用,回收之前,queue就是作为弱引用的构造传入的队列,在垃圾回收的时候,
// 如果发现某个对象只被弱引用引用,那么这个对象就会被回收,并且在回收之前,会把这个弱引用添加到它构造方法中弱引用关联的队列中。
// 在watch方法中,为观察的对象创建了一个弱引用,如果这个观察的目标被垃圾回收了,那么这里的弱引用就会被放入到队列当中。
// 如果能够在这个里边取出弱引用,那么就说明观察的对象就被回收了,所以就从怀疑名单中给移除掉。如果经过了这一系列步骤。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) {
// 把观察对象的ID从retainedKeys移除掉
retainedKeys.remove(ref.key);
}
}
}
(2)发现内存泄漏,就会打印内存堆栈:
java
public File dumpHeap() {
// 1. 创建一个用于存放堆转储文件的文件对象
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
// 如果创建文件失败(返回 RETRY_LATER,代表无法生成文件),则直接返回
if (heapDumpFile == RETRY_LATER) {
return RETRY_LATER;
}
// 2. 创建一个 FutureResult 对象,用于等待 Toast 显示完成
FutureResult<Toast> waitingForToast = new FutureResult<>();
// 显示一个 Toast(提示用户正在进行堆转储),并将结果通过 waitingForToast 传递回来
showToast(waitingForToast);
// 等待最多 5 秒钟,等待 Toast 显示完成
if (!waitingForToast.wait(5, SECONDS)) {
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
// 如果等待超时,则返回 RETRY_LATER,放弃本次堆转储
return RETRY_LATER;
}
// 3. 构建一个通知,用于提示用户正在进行堆转储操作
Notification.Builder builder = new Notification.Builder(context)
.setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
// 通过内部工具方法构建通知对象
Notification notification = LeakCanaryInternals.buildNotification(context, builder);
// 获取系统的通知管理器
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// 生成一个基于系统时间的通知 ID
int notificationId = (int) SystemClock.uptimeMillis();
// 发布通知,提示用户堆转储正在进行
notificationManager.notify(notificationId, notification);
// 4. 从 waitingForToast 中获取之前显示的 Toast 对象
Toast toast = waitingForToast.get();
try {
// 5. 调用 Debug.dumpHprofData() 方法进行堆转储,
// 参数为堆转储文件的绝对路径,Framework 提供该能力,将堆数据写入文件
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
// 6. 堆转储成功后,取消之前显示的 Toast
cancelToast(toast);
// 7. 同时取消通知
notificationManager.cancel(notificationId);
// 返回生成的堆转储文件
return heapDumpFile;
} catch (Exception e) {
// 如果在堆转储过程中出现异常,则记录日志
CanaryLog.d(e, "Could not dump heap");
// 并返回 RETRY_LATER,表示本次堆转储失败,需要重试
return RETRY_LATER;
}
}
(3)HeapAnalyzerService中的一个子线程用来处理分析结果
java
@Override
protected void onHandleIntentInForeground(@Nullable Intent intent) {
// 如果 intent 为 null,则记录日志并忽略此次调用
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
// 从 intent 中获取用于接收分析结果的 listener 的类名
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
// 从 intent 中获取 HeapDump 对象,HeapDump 包含堆转储文件、排除引用配置、计算保留内存大小的标志等
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
// 创建一个 HeapAnalyzer 实例,用于分析堆转储文件
// 参数包括:排除的引用列表、当前上下文(this),以及堆转储时使用的 ReachabilityInspector 类数组
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
// 开始堆转储分析,检查泄漏情况,返回一个 AnalysisResult 对象,
// 其中包含泄漏路径、泄漏原因等信息
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
// 将分析结果发送给指定的 listener 服务,最终展示给用户或者上报服务器,这个listener就是最开始我们注册的时候:refWatcher(application).listenerServiceClass(DisplayLeakService.class)
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
// 必须使用弱引用而不能使用虚引用,因为虚引用无法获取到引用的对象。但是下边的流程中我们是需要这个对象的。
// 该方法用于在堆转储快照中查找与指定 key 对应的泄漏对象
// 这里查找的是通过 KeyedWeakReference 包装的对象的弱引用
private Instance findLeakingReference(String key, Snapshot snapshot) {
// 在快照中查找 KeyedWeakReference 类(该类用于包装被监控对象的弱引用)
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.");
}
// 用于存储遍历过程中找到的所有 key 值,便于出错时打印调试信息
List<String> keysFound = new ArrayList<>();
// 遍历堆快照中所有 KeyedWeakReference 的实例
for (Instance instance : refClass.getInstancesList()) {
// 获取当前实例所有字段的值列表
List<ClassInstance.FieldValue> values = classInstanceValues(instance);
// 从字段列表中取出名称为 "key" 的字段的值
Object keyFieldValue = fieldValue(values, "key");
if (keyFieldValue == null) {
// 如果当前实例的 key 字段为空,则记录 null 并继续下一个实例
keysFound.add(null);
continue;
}
// 将 key 字段值转换为字符串
String keyCandidate = asString(keyFieldValue);
// 如果转换后的 key 与传入的目标 key 相等,则说明找到了对应的弱引用
// 返回该实例中保存被监控对象的字段 "referent" 的值,即真正可能泄漏的对象
if (keyCandidate.equals(key)) {
return fieldValue(values, "referent");
}
// 将遍历到的 keyCandidate 添加到 keysFound 列表中,便于调试输出
keysFound.add(keyCandidate);
}
// 如果遍历完所有实例都没有找到匹配的 key,则抛出异常,并输出所有遍历到的 key 信息
throw new IllegalStateException(
"Could not find weak reference with key " + key + " in " + keysFound);
}
// 分析堆转储文件,检查是否存在内存泄漏,并返回一个 AnalysisResult 对象
// 参数:heapDumpFile - 堆转储文件;referenceKey - 被监控对象的唯一标识;computeRetainedSize - 是否计算保留内存大小
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 用于解析堆转储数据
HprofParser parser = new HprofParser(buffer);
// 更新进度:正在解析堆转储文件
listener.onProgressUpdate(PARSING_HEAP_DUMP);
// 解析堆转储数据,生成 Snapshot 对象,代表整个堆的快照
Snapshot snapshot = parser.parse();
// 更新进度:正在去重 GC Roots(垃圾回收根节点)
listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
// 对 GC Roots 进行去重,简化后续分析过程
deduplicateGcRoots(snapshot);
// 更新进度:正在查找泄漏的引用
listener.onProgressUpdate(FINDING_LEAKING_REF);
// 在堆快照中查找与 referenceKey 对应的泄漏对象
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
// 如果返回的泄漏对象为 null,说明在关键时间段内目标对象已经被回收
// 这里视为误报(false alarm),返回 noLeak 结果,附带泄漏对象的类名和分析耗时
if (leakingRef == null) {
String className = leakingRef.getClassObj().getClassName();
return noLeak(className, since(analysisStartNanoTime));
}
// 如果找到了泄漏对象,则进一步查找泄漏引用路径(Leak Trace),
// 并返回包含详细泄漏信息的 AnalysisResult 对象
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
// 如果在整个过程中出现异常,则返回失败的分析结果,并附上耗时信息
return failure(e, since(analysisStartNanoTime));
}
}
6. 一些细节的补充与2.0后版本的调整
6.1 正确触发 GC
不重写finalize方法,怎么知道对象是否被回收?三种弱引用都有一个同样的功能,当GC开始时候,如果垃圾回收处理器发现,这个弱引用所引用的对象没有被其他强引用引用到,那么在垃圾回收之前,就会把这个弱引用本身添加到队列当中。注意,这里是引用的对象,而不是弱引用引用的对象。这样就能判断这个对象能否被GC了,finalize方法并不可靠 ,它在GC调用的时候不一定会被调用。官方JDK的实现,也是通过这种方式做的。
- 可以通过调用
dumpHprofData
获取 hprof 文件来进行堆内存分析。
6.2 LeakCanary2.0的初始化流程简单说明:
6.3 LeakCanary2.0与1.0的一些差别:
- 2.0支持对View以及Service的内存泄漏的监听了,RootViewWatcher 通过监听根视图的添加和其 attach/detach 状态,这里还涉及到另一个库 Curtains,这个库的作用就是可以监听 window 的生命周期。在视图从窗口移除时启动延迟检查,检测它是否能被回收,从而实现对根视图内存泄漏的监控,而Service则是通过反射获取 ActivityThread 中的 Handler(mH)和它的 mCallback 字段,并用自定义的 Callback 替换来实现的:
kotlin
private fun swapActivityThreadHandlerCallback(swap: (Handler.Callback?) -> Handler.Callback?) {
val mHField =
activityThreadClass.getDeclaredField("mH").apply { isAccessible = true }
val mH = mHField[activityThreadInstance] as Handler
val mCallbackField =
Handler::class.java.getDeclaredField("mCallback").apply { isAccessible = true }
val mCallback = mCallbackField[mH] as Handler.Callback?
mCallbackField[mH] = swap(mCallback)
}
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
- 导入包发生变化,2.0有两种导入方式:
kotlin
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14' // 主进程分析
debugImplementation 'com.squareup.leakcanary:leakcanary-android-process:2.14' // 独立进程分析内存
泄漏
不同的配置方式会让LeakCanary运行在不同的进程中,下图就是独立进程:
-
初始化时机: : Android 中
ContentProvider#onCreate()
会在Application#onCreate()
之前执行, LeakCanary 2.0 利用了这一特性,实现了自动初始化,无需手动调用初始化代码。 -
堆文件分析方式变化:
- 1.x 版本: : 内部使用第三方库
haha
进行堆文件分析; - 2.x 版本: :改用第三方库
shark
,内存占用大幅减少,分析速度大幅提升。
- 1.x 版本: : 内部使用第三方库
-
分析流程内部逻辑的变化:
- 延迟检查(与 1.x IdleHandler 的区别): 1.x 版本使用主线程空闲(IdleHandler)来触发检查;2.x 版本改为在主线程中延迟执行 5 秒(或其他指定时间),给系统足够的时间进行垃圾回收。
AppWatcher
/ObjectWatcher
的引入 :1.x 版本: 主要通过RefWatcher
对象来监控 Activity、Fragment 等,手动在 Activity/Fragment 销毁时调用watch()
;2.x 版本: 引入了AppWatcher
和ObjectWatcher
,更集中地管理所有被观察对象;对外只需调用expectWeaklyReachable()
或watch()
,内部会自动安排延迟检测并在确认对象未被回收后触发后续分析。
java
@Synchronized
override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
// 如果监控功能未启用,则直接返回
if (!isEnabled()) {
return
}
// 先尝试移除已经被回收的对象,确保 watchedObjects 中只保留仍然存活的对象引用
removeWeaklyReachableObjects()
// 为要监控的对象生成一个唯一的 key
val key = UUID.randomUUID().toString()
// 记录当前时间(毫秒),用于后续判断对象存活时长
val watchUptimeMillis = clock.uptimeMillis()
// 创建一个 KeyedWeakReference,用弱引用的方式关联被监控对象
// queue: ReferenceQueue,用于在对象被回收时将弱引用加入队列
val reference = KeyedWeakReference(
referent = watchedObject,
key = key,
description = description,
watchUptimeMillis = watchUptimeMillis,
queue = queue
)
// 打印调试日志,说明正在监控哪个对象(包括对象类型、描述信息以及生成的 key)
SharkLog.d {
"Watching " +
(if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
(if (description.isNotEmpty()) " ($description)" else "") +
" with key $key"
}
// 将弱引用对象存储到 watchedObjects 字典中,key 用于标识该对象
watchedObjects[key] = reference
// 与 1.x 不同:1.x 使用主线程的 IdleHandler 来等待系统空闲后检查;
// 2.x 改用一个延迟任务(通常延迟 5 秒),给对象足够时间变为不可达并被 GC
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
@Synchronized
private fun moveToRetained(key: String) {
// 再次尝试移除已经被 GC 回收的弱引用对象
removeWeaklyReachableObjects()
// 从 watchedObjects 中获取对应 key 的引用
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
// 如果还能找到该引用,说明对象仍然存活(尚未被 GC 回收)
// 记录对象被确认为"保留"时的时间
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
// 通知所有 OnObjectRetainedListener 监听器,表示有对象真正被保留了
// 这通常意味着可能存在内存泄漏,需要后续进行进一步分析
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
// 在初始化(例如 AppWatcher.objectWatcher.addOnObjectRetainedListener(this))时
// 会将当前对象注册到 onObjectRetainedListeners 中,
// 当 moveToRetained 确认对象尚未被回收时,就会回调相应的监听器。