第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 现象识别故障树

在接到异常反馈后,可按下图快速建立泄漏假设。故障树从应用异常出发,逐层筛选,最终指向内存泄漏嫌疑高低。

内存泄漏现象识别故障树 应用异常表现 OOM崩溃 GC频繁 ANR 内存持续增长 低内存警告 内存泄漏嫌疑评估:高(需MAT/LeakCanary验证) 说明:单一现象不能确定泄漏,但多种现象同时出现(如OOM+GC频繁+内存持续增长)时,泄漏概率极高。 应立即采集堆转储(HPROF)和Logcat进行后续分析。

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 对关键页面做快速泄漏检测。