第 2 章 泄漏现象识别:GC不可达、内存持续增长、OOM崩溃

内存泄漏的第一道防线是准确识别泄漏现象。本章将建立“现象识别 → 数据采集 → 原因定位 → 修复动作 → 复测 → 预防”的完整排查链路,聚焦于 LifecycleRegistry 引用泄漏最典型的三个观测指标:GC 不可达对象、堆内存持续攀升、最终 OOM 崩溃。以下是一张故障推理树,帮助你从现象快速收敛到根因。

LifecycleRegistry 引用泄漏 → 现象推理树 内存泄漏发生 ① GC 不可达 对象仍被强引用 ② 内存持续增长 GC 无法释放 ③ OOM 崩溃 堆耗尽 → 根因:LifecycleRegistry 引用链 LifecycleOwner (Activity) mLifecycleRegistry 持有 Observer 强引用 回调未及时移除 三个现象逐级递进,不可达是前兆,增长是过程,OOM 是后果 数据采集工具:Android Profiler / Heap Dump / GC 日志 分析路径:观察对象 → 检查引用链 → 定位 LifecycleRegistry
泄漏现象判断规则:三种现象构成逻辑链条。只要观察到任意一个,就应当启动泄漏排查流程,不可等到 OOM 才介入。

2.1 现象识别

现象表现特征观测手段
GC 不可达对象(Activity/Fragment)在应该销毁后仍被 GC Root 强引用,导致无法回收Heap Dump 分析,查看对象引用链
内存持续增长反复进入/退出页面后,堆内存占用阶梯式上升,每次退出后无法回落Android Profiler 实时监视堆;
adb shell dumpsys meminfo
OOM 崩溃App 突然退出,logcat 打印 OutOfMemoryError,堆快照显示 90%+ 占用捕获 logcat 日志;分析 Tombstone / hprof

2.2 数据采集

在怀疑 LifecycleRegistry 泄漏时,应系统采集以下数据:

  • 堆快照:使用 Debug.dumpHprofData() 或 Android Profiler 导出 .hprof 文件,转换后用 MAT / Memory Analyzer 打开。
  • GC 日志:通过 adb logcat -s "dalvikvm" "*:D" 观察 GC 回收行为是否异常。
  • 内存趋势:使用 adb shell dumpsys meminfo <package> -d 获取进程内存明细,重点关注 Native Heap 和 Java Heap。
# 采集堆快照示例
adb shell am dumpheap com.example.app /data/local/tmp/dump.hprof
adb pull /data/local/tmp/dump.hprof ./

2.3 原因定位

根据采集的堆快照,查看 com.android.internal.util.LifecycleRegistry 内部维护的 mObservers 列表。典型的泄漏引用链:

Thread main → Activity → LifecycleRegistry → mObservers
    → ObserverWrapper → Observer (Fragment/Activity 回调对象)

若发现多个本应销毁的 Activity 实例仍被 LifecycleRegistry 持有,即可确认引用泄漏。

2.4 修复动作(概要)

修复的核心是斩断强引用链:

  • 在 onDestroy() 中显式移除 Observer:lifecycle.removeObserver(observer)
  • 使用 WeakReference 包装回调对象
  • 改用 LifecycleEventObserver 并主动解注册

详细修复步骤将在第 5~8 章展开。

2.5 复测

  1. 清空应用内存,反复进入/退出目标页面 10~20 次。
  2. 使用 Android Profiler 实时监视堆内存曲线,确认每次退出后内存回落到基线。
  3. 抓取一次堆快照,检查 LifecycleRegistry.mObservers 中是否仍有已销毁的 Activity 引用。
  4. 若 OOM 复现,则继续调试;否则标记为已修复。
常见陷阱:仅凭内存大小波动不能断言泄漏。务必结合 GC 不可达对象计数 与 引用链追踪 双重验证,避免误判。

2.6 预防

从架构层面杜绝 LifecycleRegistry 泄漏:

  • 避免在 onCreate/onStart 中注册匿名内部类 Observer 而不解注册。
  • 使用 LifecycleBoundObserver 或 DefaultLifecycleObserver 时,确保宿主销毁时自动移除。
  • 在代码审查环节加入“生命周期注册/解注册成对检查”规则。
  • 编写单元测试,通过 LifecycleRegistry.markState() 模拟销毁并验证 Observer 列表清空。