第4章 堆转储获取实战

在Kotlin内存泄漏排查中,堆转储文件(Heap Dump/Hprof)是还原泄露路径、定位泄漏引用的核心证据。本章聚焦四种实战获取手段:Android Profiler导出、adb命令抓取、OOM时自动dump以及转储文件压缩与传输。通过现象识别→数据采集→原因定位→修复动作→复测→预防的闭环,掌握不同场景下如何稳定、高效地获取堆转储。

前置条件:确保设备已开启开发者选项、USB调试;Android Profiler仅支持Android 5.0(API 21)及以上;自动dump需要应用代码支持或使用LeakCanary等工具。

4.1 现象识别:什么情况下需要抓取堆转储?

  • OOM崩溃:日志中出现 OutOfMemoryError、java.lang.OutOfMemoryError: Failed to allocate 等关键字。
  • GC频繁导致卡顿:Systrace或Logcat显示GC次数>20次/分钟,且内存无法稳定回收。
  • 内存曲线持续上升:Android Profiler或dumpsys meminfo显示进程PSS每5分钟增长超过50MB。
  • LeakCanary检测到泄漏:LeakCanary通知已有对象泄漏,需获取原始hprof做深度分析。
注意:堆转储会暂停应用(约200ms~1s),不要在用户核心操作路径上频繁触发;生产环境建议配合条件判断(如仅当内存>80%阈值时触发)。

4.2 数据采集:四种获取方式详解

下面通过流程图总览四种方式的接入点与决策路径:

堆转储获取决策与流程 堆转储获取决策流程 需要堆转储? Android Profiler AS > Profiler > 内存 > Dump adb shell am dumpheap 命令行实时抓取 OOM自动dump Runtime.addOnUncaughtExceptionHandler LeakCanary自动获取 检测到泄漏时自动dump 压缩 → 传输到分析主机 gzip / adb pull / scp 📌 选型依据:目标是否可安装AS、是否需要自动化、是否需保留原始hprof

4.3 四种获取方式详解

4.3.1 Android Profiler导出

适用于开发阶段,已连接Android Studio,可交互查看对象树。

  1. 打开Android Studio > View > Tool Windows > Profiler。
  2. 选择目标设备和进程,点击Memory时间线。
  3. 点击Dump Java heap按钮(绿色垃圾桶图标旁)。
  4. 等待几秒,自动生成.hprof文件并显示在Profiler下方。
  5. 右键选择Export to file...保存。
注意:Profiler导出的.hprof是Android格式,不能直接被MAT识别。需先通过 hprof-conv 工具转换为标准Java堆转储:
hprof-conv -z input.hprof output.hprof

4.3.2 adb命令抓取

适合无AS环境、CI自动化、批量抓取。无需root。

场景命令说明
抓取指定进程
adb shell am dumpheap com.example.app /sdcard/dump.hprof
进程名必须完整;文件保存到设备。
拉取到本地
adb pull /sdcard/dump.hprof ./
注意权限:部分设备需先 adb shell chmod 777 /sdcard/dump.hprof
支持重复抓取
adb shell am dumpheap --user 0 com.example.app /data/local/tmp/dump.hprof
/data/local/tmp 避免权限问题。
陷阱:am dumpheap 只抓取Java堆,不包含Native堆;若怀疑Native内存泄漏需另用 am nativeheap 或Profiler的Native内存分析。

4.3.3 OOM自动dump

在生产环境捕捉崩溃前的堆状态。通过注册全局未捕获异常处理器实现。

Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
    if (throwable is OutOfMemoryError) {
        val pid = Process.myPid()
        val path = "${context.cacheDir}/oom_${pid}.hprof"
        // 注意:OOM时可能无法分配Java内存,推荐在JNI层用C++去dump
        // 简易方案:fork process后执行 am dumpheap
        val cmd = "am dumpheap $pid $path"
        Runtime.getRuntime().exec(cmd)
        // 保存现场后可以立即崩溃或等待dump完成
    }
    // 调用原handler
    originalHandler?.uncaughtException(thread, throwable)
}

更可靠的自动dump方案可使用LeakCanary:在应用初始化时配置,当检测到对象未被回收时自动抓取。

4.3.4 转储文件压缩与传输

原始.hprof文件可能超过200MB,传输前必须压缩。

方法命令/工具压缩率
设备端gzip
adb shell "gzip -f /sdcard/dump.hprof"
40%~60%
本地压缩
gzip -9 dump.hprof
30%~50%
分次拉取
adb pull /sdcard/dump.hprof.gz ./
大文件可断点续传(配合adb shell split)
网络直传sftp
scp user@host:/path/dump.hprof.gz ./
需设备已连内网或使用代理

4.4 原因定位:选型判断规则

根据实际环境和目的,按以下故障树选择采集手段:

  1. 开发/调试 → 首选Android Profiler,交互直观,可实时查看对象。
  2. 回归测试/CI → 使用adb命令脚本化,批量抓取。
  3. 线上OOM问题 → 使用LeakCanary或自定义UncaughtExceptionHandler自动dump。
  4. 快速分析需求 → 直接adb dumpheap并拉取,无需安装AS。
  5. 文件过大无法传输 → 设备端先gzip压缩再adb pull。
判断规则速查表:
条件推荐方法关键约束
已连接ASAndroid Profiler导出需暂停应用
无GUI环境adb命令需知道包名
生产崩溃场景OOM自动dump注意OOM时内存紧张,优先JNI层方案
泄漏检测工具LeakCanary自动获取仅当配置LeakCanary后生效

4.5 修复动作:常见问题与调整

  • hprof文件损坏:adb pull时文件被截断 → 重新抓取,或改用 adb pull --sync 保证完整性。
  • dropheap失败:进程无权限或已死亡 → 确认进程是否在运行,使用 adb shell ps | grep com.your.app 检查。
  • 自动dump重复触发:在UncaughtExceptionHandler中加标志位,避免递归。
  • 压缩后体积仍大:改用 gzip --fast 加快速度,或用 xz 获得更高压缩率(需设备支持)。

4.6 复测与验证

  1. 抓取成功后,确认文件大小非零,可通过 ls -lh 检查。
  2. 将.hprof.gz解压后使用MAT打开,验证能显示对象树。
  3. 对比多次抓取的内存快照,确认内存曲线是否持续上升。
  4. 若使用自动dump,模拟OOM场景(如大量加载图片)验证触发。

4.7 预防:堆转储采集规范

  • 建立版本管理:每个hprof文件命名包含 日期_应用版本_进程ID。
  • 设置空间预警:自动dump前检查设备剩余空间 > 200MB + 预估堆大小。
  • 定期清理旧文件:adb shell rm /sdcard/*.hprof 或使用Android自带的存储管理。
  • 配置自动上传:在WIFI环境下自动将压缩后的hprof上传至分析服务器,减少人工操作。
推荐工具链:
- 自动化采集:adb shell am dumpheap + shell脚本
- 日志关联:同时抓取logcat到同一目录,便于对照时间点
- 批量转换:使用 hprof-conv 配合 find -exec 批量转换
- 云存储:将hprof.gz上传到S3/OSS,用MAT客户端直接解析