🔍 第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分析识别哪些对象无法被回收,并回溯引用路径
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(推荐)
- 打开Android Studio → 点击右下角 Profiler 面板。
- 选择目标设备/进程,点击 MEMORY 图表。
- 点击 Dump Java heap 按钮(📥图标)。
- 等待dump完成,自动加载到Profiler中。可直接搜索类名(如 OkHttpClient、Retrofit、Call)检查实例数。
- 如需导出为MAT可读格式:点击右上角 Export → 选择 Export to .hprof。
⚠️ 注意
Android Studio导出的是Android专属.hprof,MAT需要转换。使用
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-conv | Android专用.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 Reference | native层持有的对象 | OkHttp的NativeCrypto引用 |
| System Class | BootClassLoader加载的类 | 较少直接相关 |
2.5.2 在MAT中执行GC Root分析
- 打开MAT → File → Open Heap Dump → 选择转换后的.hprof。
- 点击 Histogram → 输入怀疑的类名(如
OkHttpClient)→ 查看实例数。 - 右键实例 → Merge Shortest Paths to GC Roots → 选择 exclude all phantom/weak/soft etc.(只看强引用)。
- 结果会展示从GC Root到泄漏对象的完整路径。常见模式:
GC Root (静态字段) → Thread → Callback → Activity → ... GC Root (线程) → Dispatcher → AsyncCall → Activity - 也可以使用MAT提供的 Leak Suspects Report(自动扫描“最可能”泄漏的路径)。
2.6 排查表:泄漏特征与GC Root对应规则
| 泄漏表象 | 可疑GC Root | 典型对象 | 判断规则 |
|---|---|---|---|
| Activity/Fragment无法销毁 | 静态变量 / 匿名内部类 / Handler | Retrofit中的Call对象持有Activity context | 如果Activity的onDestroy后,MAT中仍存在其引用且被一个非Activity的强引用路径链接 → 泄漏 |
| OkHttpClient实例数持续增长 | 静态变量 / 线程 | 每次创建新client但未关闭 | Heap dump中超过1个OkHttpClient实例且连接池/线程池活跃 → 需检查创建处是否复用 |
| Retrofit接口代理对象泄漏 | 线程(Dispatcher) | 未完成的网络请求持有回调 | 如果后台线程池中有待执行的Call,并且该Call持有外部引用 → 泄漏源 |
📐 判断规则提炼
“一个对象本该被GC回收,却因为存在一条从GC Root出发的强引用链而留在堆中,这就是泄漏。在MAT中,只看强引用路径(排除弱/软/虚引用),然后顺着路径找到根——通常是 static 变量、线程栈或JNI全局引用。”
“一个对象本该被GC回收,却因为存在一条从GC Root出发的强引用链而留在堆中,这就是泄漏。在MAT中,只看强引用路径(排除弱/软/虚引用),然后顺着路径找到根——通常是 static 变量、线程栈或JNI全局引用。”
2.7 本章流程图:Heap Dump获取与GC Root分析
下图概括了从现象到拿到GC Root链路的完整步骤:
2.8 本章小结
- Heap dump是排查泄漏的“第一现场”。
- 推荐优先使用 Android Studio Profiler 抓取快速分析,复杂场景用 MAT 做GC Root路径追踪。
- 分析时只看强引用(排除弱/软/虚),找到从GC Root到目标对象的唯一路径。
- 常见的OkHttp/Retrofit泄漏GC Root包括:静态OkHttpClient实例、后台Dispatcher线程、未取消的Call持有的匿名回调。