第 2 章 泄漏现象识别:GC不可达、内存持续增长、OOM崩溃
内存泄漏的第一道防线是准确识别泄漏现象。本章将建立“现象识别 → 数据采集 → 原因定位 → 修复动作 → 复测 → 预防”的完整排查链路,聚焦于 LifecycleRegistry 引用泄漏最典型的三个观测指标:GC 不可达对象、堆内存持续攀升、最终 OOM 崩溃。以下是一张故障推理树,帮助你从现象快速收敛到根因。
泄漏现象判断规则:三种现象构成逻辑链条。只要观察到任意一个,就应当启动泄漏排查流程,不可等到 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 复测
- 清空应用内存,反复进入/退出目标页面 10~20 次。
- 使用 Android Profiler 实时监视堆内存曲线,确认每次退出后内存回落到基线。
- 抓取一次堆快照,检查
LifecycleRegistry.mObservers中是否仍有已销毁的 Activity 引用。 - 若 OOM 复现,则继续调试;否则标记为已修复。
常见陷阱:仅凭内存大小波动不能断言泄漏。务必结合 GC 不可达对象计数 与 引用链追踪 双重验证,避免误判。
2.6 预防
从架构层面杜绝 LifecycleRegistry 泄漏:
- 避免在
onCreate/onStart中注册匿名内部类 Observer 而不解注册。 - 使用
LifecycleBoundObserver或DefaultLifecycleObserver时,确保宿主销毁时自动移除。 - 在代码审查环节加入“生命周期注册/解注册成对检查”规则。
- 编写单元测试,通过
LifecycleRegistry.markState()模拟销毁并验证 Observer 列表清空。