🔍 第2章:泄漏现场采集 — Heap dump获取、MAT/Android Profiler工具准备、GC Root分析

2.1 为什么需要「数据采集」阶段

泄漏现象已经出现(章节1),但只有物理世界的“卡顿、OOM、内存上涨”还不够。要精准定位泄漏代码,必须拿到堆内存快照(Heap dump),再通过GC Root分析找到引用链。本章是排查链路的第二环——把现象转成可分析的数字证据。

🎯 核心目标
 1. 获取App运行中的Java堆dump文件(.hprof)
 2. 用MAT或Android Profiler加载dump
 3. 通过GC Root分析识别哪些对象无法被回收,并回溯引用路径

2.2 故障树:从现象到Heap dump的决策链路

以下故障树帮助你快速决定“什么时候该抓dump、用什么工具抓”。

内存问题现象
 ┌─ 反复GC导致卡顿(Logcat看到GC_FOR_ALLOC频繁)
 └─ OOM崩溃(如OutOfMemoryError)
 ↓
是否需要Heap dump?
 ├─ 是 → 能复现 → 用Android Profiler抓(推荐)
 │           └─ 不能复现 → 用adb抓保留现场
 └─ 否 → 先看Logcat/静态分析(非本章范围)
 ↓
工具选择:
 ├─ Android Studio自带的Profiler(实时+离线)
 └─ MAT(Eclipse Memory Analyzer)——专攻大堆深层分析

2.3 数据采集:Heap dump获取

2.3.1 方法一:Android Studio Profiler(推荐)

  1. 打开Android Studio → 点击右下角 Profiler 面板。
  2. 选择目标设备/进程,点击 MEMORY 图表。
  3. 点击 Dump Java heap 按钮(📥图标)。
  4. 等待dump完成,自动加载到Profiler中。可直接搜索类名(如 OkHttpClient、Retrofit、Call)检查实例数。
  5. 如需导出为MAT可读格式:点击右上角 Export → 选择 Export to .hprof。
⚠️ 注意
Android Studio导出的是Android专属.hprof,MAT需要转换。使用 hprof-conv 工具(SDK tools目录):
hprof-conv original.hprof converted.hprof
转换后即可用MAT分析。

2.3.2 方法二:命令行adb dump(适合无法连接Studio的场景)

当App已崩溃或现场需远程保留时,用adb命令抓取:

# 获取进程PID(假设包名为 com.example.app)
adb shell ps | grep com.example.app

# 触发dump(PID为12345)
adb shell am dumpheap 12345 /data/local/tmp/heapdump.hprof

# 拉取到本地
adb pull /data/local/tmp/heapdump.hprof ./heapdump.hprof

导出后同样需要 hprof-conv 转换(若在非Android Studio环境,也可使用MAT自带转换工具)。

2.4 工具准备与配置

工具用途安装/配置要点
Android Studio Profiler实时监控内存、快速抓dump、基础对象分析Android Studio 3.0+自带,无需额外安装
MAT (Eclipse Memory Analyzer)深度GC Root分析、可疑泄漏报告、直方图对比1. 官网下载 standalone版
2. 需JDK 11+
3. 调整 MemoryAnalyzer.ini 加大 -Xmx(推荐4g以上)
hprof-convAndroid专用.hprof → 标准Java hprof位于Android SDK platform-tools 目录;或直接使用MAT的 ParseHeapDump.sh 脚本转换

2.5 GC Root分析:定位泄漏的“锚点”

GC Root 是JVM/ART在判定对象是否可回收时的起点。对象若从任何GC Root可达,则不回收。泄漏对象通常被某个长生命周期的GC Root(如static变量、线程、JNI引用)强引用着。

2.5.1 常见GC Root类型(Android/Java)

GC Root类型举例在OkHttp/Retrofit中的典型泄漏场景
Class(类静态变量)static OkHttpClient client全局OkHttpClient未关闭,内部线程池和连接池持续活跃
Thread(活跃线程)Dispatcher中的线程Retrofit的Call在后台线程未取消,导致持有Activity引用
JNI Global Referencenative层持有的对象OkHttp的NativeCrypto引用
System ClassBootClassLoader加载的类较少直接相关

2.5.2 在MAT中执行GC Root分析

  1. 打开MAT → File → Open Heap Dump → 选择转换后的.hprof。
  2. 点击 Histogram → 输入怀疑的类名(如 OkHttpClient)→ 查看实例数。
  3. 右键实例 → Merge Shortest Paths to GC Roots → 选择 exclude all phantom/weak/soft etc.(只看强引用)。
  4. 结果会展示从GC Root到泄漏对象的完整路径。常见模式:
    GC Root (静态字段) → Thread → Callback → Activity → ...
    GC Root (线程) → Dispatcher → AsyncCall → Activity
  5. 也可以使用MAT提供的 Leak Suspects Report(自动扫描“最可能”泄漏的路径)。

2.6 排查表:泄漏特征与GC Root对应规则

泄漏表象可疑GC Root典型对象判断规则
Activity/Fragment无法销毁静态变量 / 匿名内部类 / HandlerRetrofit中的Call对象持有Activity context如果Activity的onDestroy后,MAT中仍存在其引用且被一个非Activity的强引用路径链接 → 泄漏
OkHttpClient实例数持续增长静态变量 / 线程每次创建新client但未关闭Heap dump中超过1个OkHttpClient实例且连接池/线程池活跃 → 需检查创建处是否复用
Retrofit接口代理对象泄漏线程(Dispatcher)未完成的网络请求持有回调如果后台线程池中有待执行的Call,并且该Call持有外部引用 → 泄漏源
📐 判断规则提炼
“一个对象本该被GC回收,却因为存在一条从GC Root出发的强引用链而留在堆中,这就是泄漏。在MAT中,只看强引用路径(排除弱/软/虚引用),然后顺着路径找到根——通常是 static 变量、线程栈或JNI全局引用。”

2.7 本章流程图:Heap Dump获取与GC Root分析

下图概括了从现象到拿到GC Root链路的完整步骤:

Heap Dump获取与GC Root分析流程 Heap Dump 采集 → GC Root 分析流程 ① 内存现象触发(卡顿/OOM) ② Android Profiler 抓dump ③ adb dumpheap 命令 ④ hprof-conv 转换为标准格式 ⑤ MAT 打开 .hprof 文件 ⑥ 执行「Merge Shortest Paths to GC Roots(强引用)」 💡 如果使用Android Profiler直接分析(不导出),可跳过步骤④⑤,但MAT的GC Root路径报告更详细

2.8 本章小结

  • Heap dump是排查泄漏的“第一现场”。
  • 推荐优先使用 Android Studio Profiler 抓取快速分析,复杂场景用 MAT 做GC Root路径追踪。
  • 分析时只看强引用(排除弱/软/虚),找到从GC Root到目标对象的唯一路径。
  • 常见的OkHttp/Retrofit泄漏GC Root包括:静态OkHttpClient实例、后台Dispatcher线程、未取消的Call持有的匿名回调。