3. 常用排查工具介绍:Android Profiler、LeakCanary、MAT、JProfiler
在 ViewModel 持有 Activity 的场景中,仅靠代码审查很难发现潜伏的泄漏。本章引入四款工业级排查工具,并按照“现象识别 → 数据采集 → 原因定位 → 修复动作 → 复测 → 预防”的排查型课程骨架,说明每款工具在哪个阶段发挥核心作用,以及如何组合使用。
工具选择铁律:
LeakCanary 用于现象识别与复测;Android Profiler 负责数据采集与活证据获取;MAT 承担原因定位;JProfiler 在预防阶段做趋势与性能基线分析。四者按顺序递进,不可跳步。
LeakCanary 用于现象识别与复测;Android Profiler 负责数据采集与活证据获取;MAT 承担原因定位;JProfiler 在预防阶段做趋势与性能基线分析。四者按顺序递进,不可跳步。
3.1 现象识别阶段:LeakCanary 与 Android Profiler
| 工具 | 作用阶段 | 典型输出 | 判断规则 |
|---|---|---|---|
| LeakCanary | 现象识别 → 复测 | 堆转储通知 + “泄漏痕迹” | 若通知出现 “com.example.MainActivity” 且引用链指向 ViewModel,则确认为 ViewModel 泄漏。 |
| Android Profiler (Memory) | 数据采集 → 复测 | 实时内存曲线 + HPROF 转储 | Memory 曲线在 Activity.onDestroy() 后未明显下降,GC 后仍高于基线,视为可疑泄漏。 |
易错点:LeakCanary 默认只在 Debug 构建生效。release 包不会触发通知,不能代替性能监控。线上泄漏仍需依靠 Android Profiler 或自定义 HPROF 采集。
3.2 数据采集阶段:Android Profiler 获取 HPROF
当 LeakCanary 或用户反馈出现 “OutOfMemoryError” 时,立即使用 Android Profiler 捕获堆转储文件(HPROF)。操作步骤:
- 通过 USB 连接设备,选择目标进程。
- 点击 Dump Java heap,等待约 10–30 秒。
- 导出为
.hprof文件(Profiler 自动转换或手动使用hprof-conv)。
# 若导出为标准 .hprof,需先转换为 MAT 可识别格式
hprof-conv -z dump.hprof dump_standard.hprof
采集时机:必须正好在 Activity 进入后台并 GC 后执行 dump。最佳实践是先在 Profiler 中手动触发一次 GC(“Force garbage collection”),再立即 dump。只保留一次 dump 不能排除瞬时对象干扰,应连续采集两次 dump 对比。
3.3 原因定位阶段:MAT (Memory Analyzer Tool)
MAT 是定位泄漏根因的“解剖刀”。导入标准 HPROF 后,按故障树执行判断:
- 第一步:检查
com.example.MainActivity对象的 GC Root 路径(Path to GC Roots → exclude weak references)。 - 第二步:若发现
ViewModel实例通过mActivityRef强引用指向 Activity,则定位为 ViewModel 间接持有 Activity 引用。 - 第三步:使用 Dominator Tree 查看 ViewModel 实例持有的重对象,检查是否包含 WebView、Fragment、Drawable 等大对象。
| MAT 视图 | 排查表 | 判断规则 |
|---|---|---|
| Histogram | 按类名过滤泄漏 Activity 实例数 | 若 MainActivity 实例数 > 1,且每个实例都有不同 GC Root 路径,则为重复泄漏。 |
| Path to GC Roots | 逐层展开引用链 | 若路径中出现 ViewModel.store.mActivityRef → Activity,则判定为 ViewModel 泄漏。 |
| Dominator Tree | 查看 retained heap 最大的对象 | 若 ViewModel 实例 retained heap > 10MB,且子节点包含 Activity 自身,则必须修复。 |
3.4 修复动作验证:LeakCanary 复测
修复代码(如移除 ViewModel 内的 Activity 字段或改用 Application context)后,必须运行 LeakCanary 确认泄漏不再出现。
// 修复前:ViewModel 内部持有 Activity 引用
public class MyViewModel extends ViewModel {
private Activity mActivity; // 泄漏源
public MyViewModel(Activity activity) {
this.mActivity = activity;
}
}
// 修复后:移除 Activity 引用,改为 Application context
public class MyViewModel extends ViewModel {
private Application mApp;
public MyViewModel(Application app) {
this.mApp = app;
}
}
- 重新编译并安装 Debug 包。
- 重复泄漏场景:打开→关闭 Activity 三次。
- 等待 LeakCanary 通知。若 10 秒内无通知,且
LeakCanary.INSTANCE.isLeakDisplayed()返回 false,则修复成功。
3.5 预防阶段:JProfiler 与基线监控
JProfiler 用于 预防 阶段,在 CI 或压测环境中持续监控 ViewModel 的内存占用趋势。对比每次构建的堆快照,查看 ViewModel 实例数是否随测试次数线性增长。
| 指标 | 判断规则 | 触发动作 |
|---|---|---|
| ViewModel 实例数 | 超过基线值的 20% 或持续 5 次构建递增 | 自动触发 JProfiler 的 HPROF dump,归档至 CI 制品。 |
| Retained Heap | 单次增长超过 5MB | 发送告警给开发团队,要求排查新代码是否引入 Activity 引用。 |
预防最佳实践:在 JProfiler 中设置“ViewModel 最大实例数”告警规则,配合 GitLab CI / GitHub Actions 流水线,每次合并前自动执行一次内存快照比对。只有通过基线校验的 MR 才允许合并。
以上四款工具的组合使用,覆盖从“泄漏发生”到“预防复发”的完整闭环。下节将基于 MAT 输出,讲解如何将 GC Root 链映射为代码中的具体引用关系。