第1章 泄漏现象识别:从异常表象到内存泄漏的初步判断
内存泄漏不会直接报错,而是通过一系列应用异常间接释放信号。识别这些信号是排查的第一步。本章聚焦五种最常见的外部表现,并给出从现象到内存泄漏嫌疑的快速判断规则。
1.1 五种典型泄漏现象
| 现象 | 用户/开发者感知 | 与内存泄漏的关联 |
|---|---|---|
| OOM崩溃 | App闪退,日志显示 OutOfMemoryError | 泄漏对象持续占用,堆无法分配新对象 |
| GC频繁 | Logcat大量 GC_CONCURRENT/GC_FOR_ALLOC,UI 掉帧 | 存活对象越来越多,GC 无法完全回收,触发频率升高 |
| ANR | 界面无响应,弹出 ANR 对话框 | 主线程被大量 GC 停顿阻塞,或泄漏导致内存交换 |
| 内存持续增长 | Profiler 内存曲线不回落,活动切换后基线抬高 | 泄漏对象随操作累积,存在僵尸引用链 |
| 低内存警告 | Logcat 出现 onTrimMemory(TRIM_MEMORY_COMPLETE) 等 | 系统回收压力增大,泄漏加剧内存紧张 |
1.2 现象识别故障树
在接到异常反馈后,可按下图快速建立泄漏假设。故障树从应用异常出发,逐层筛选,最终指向内存泄漏嫌疑高低。
1.3 快速排查判断规则
收到异常反馈后,按以下规则快速判断是否应将内存泄漏列为高优先级嫌疑:
- OOM崩溃:如果是
java.lang.OutOfMemoryError且伴随Failed to allocate,优先怀疑泄漏。查看崩溃前的堆总大小是否远超正常阈值。 - GC频繁:通过
adb logcat -s "dalvikvm"或"Art"观察 GC 日志。若连续多次 GC 都回收超过 50% 堆,但总占用仍上升,则泄漏嫌疑大。 - ANR:抓取
/data/anr/traces.txt,查看主线程堆栈是否卡在GC或java.lang.ref.Finalizer相关方法。 - 内存持续增长:用
adb shell dumpsys meminfo [包名]连续采样 5 次以上,观察 PSS Total 是否每次增加且不回缩。 - 低内存警告:Logcat 搜索
onTrimMemory,若频繁达到TRIM_MEMORY_COMPLETE或TRIM_MEMORY_MODERATE,同时其他表现出现,则泄漏可能性大。
💡 避坑提醒:OOM 也可能是单次大对象分配(如超大 Bitmap、巨型 byte[])造成,与累积泄漏不同。区分方法:泄漏导致的 OOM 往往在重复操作后出现,且
GC 日志显示大量 Objects 未被释放;单次大对象 OOM 通常发生在特定操作点,堆总使用量较小但请求分配过大。
1.4 数据采集前置步骤
一旦通过上述规则将内存泄漏列为高嫌疑,应立即执行以下数据采集动作,为后续章节的MAT/LeakCanary精确定位提供原始数据:
# 采集堆转储(HPROF)
adb shell am dumpheap [包名] /data/local/tmp/dump.hprof
adb pull /data/local/tmp/dump.hprof .
# 采集持续内存曲线(每2秒采样一次,共10次)
for i in $(seq 1 10); do
adb shell dumpsys meminfo [包名] | grep "TOTAL PSS" >> mem_dump.txt
sleep 2
done
# 采集GC日志(过滤关键标签,保存到文件)
adb logcat -c
adb logcat -s "dalvikvm" "ART" "art" -v time > gc_log.txt &
# 复现操作后,停止日志采集
kill %1
⚠️ 注意:采集 HPROF 时应用会短暂冻结,应避开用户高频操作时间。如果应用已崩溃,可以从
/data/anr/ 或 tombstone 中获取系统自动保存的堆转储。
1.5 预防基线设定
在未发生异常时,建议建立内存基线,便于快速识别异常增长:
- 记录稳定版本的空闲态 PSS 和最大峰值。
- 监控
ActivityManager: Low on memory日志频率。 - 定期运行 LeakCanary 的
testWatcher对关键页面做快速泄漏检测。