第1章 内存溢出故障树构建

1.1 现象识别

多媒体应用出现 OOM 时,通常表现为:

  • 应用无响应(ANR)后直接闪退;
  • Logcat 中出现 OutOfMemoryError 或 java.lang.OutOfMemoryError;
  • 系统日志提示 lowmemorykiller 杀进程;
  • 图形渲染卡顿、纹理缺失或界面黑屏。
排查起点:一旦捕获上述现象,立即保存完整的 logcat 日志和 hprof 堆转储文件,避免重启丢失现场。

1.2 数据采集

采集三类数据对应三大内存区域:

内存区域 采集工具 / 命令 关键数据
Java 堆 adb shell am dumpheap <pid> /data/local/tmp/heap.hprof 对象实例、GC 根路径、大对象数组
Native 堆 adb shell dumpsys meminfo <pid> + malloc debug Native 分配总量、backtrace、未释放指针
图形内存 adb shell dumpsys gfxinfo | dumpsys SurfaceFlinger 图形缓冲区大小、纹理数量、显存占用

1.3 原因定位 —— 故障树

OOM 故障树的根节点为 内存溢出,三个主支分别是 Java 堆、Native 堆、图形内存。每一支向下延伸出叶子判断条件,形成快速诊断路径。

故障树排查原则:从根向下逐层验证,先确认溢出区域,再定位具体对象/调用链,避免盲目优化。

下表列出各支路的关键判断条件:

支路 典型现象 采集手段 叶子判断条件
Java 堆溢出 应用崩溃,logcat 直接打印 OutOfMemoryError hprof 堆转储 + MAT 分析
  • 某个对象实例数异常(如 Bitmap、byte[])
  • GC 根泄漏(引用链未断开)
  • 频繁 ConcurrentModeFailure
Native 堆溢出 应用无 Java OOM 日志,但进程内存持续上涨直至被杀 malloc debug / address sanitizer
  • Native 分配总量 > 进程上限
  • 频繁分配大块内存(> 1MB)未释放
  • 调用栈中出现第三方库(如 GStreamer、FFmpeg)
图形内存溢出 界面花屏、滑动卡顿、SurfaceFlinger 报 buffer 不足 dumpsys SurfaceFlinger / gfxinfo
  • 纹理总大小 > GPU 可用显存
  • 未释放的 GraphicBuffer 数量激增
  • Surface 数量超过 32 上限
内存溢出故障树 内存溢出 (OOM) Java 堆溢出 Native 堆溢出 图形内存溢出 堆转储对象分析 GC 根泄漏确认 大对象数组检查 Native 分配总量检查 大块未释放内存 第三方库调用栈 纹理总量超限 GraphicBuffer 堆积 Surface 数量异常 顺着故障树从根向下验证,每一条叶子都是一个具体的排查动作。

1.4 修复动作

基于故障树定位到具体叶子后,修复方向如下:

  • Java 堆:修复内存泄漏、减少 Bitmap 尺寸、使用对象池;
  • Native 堆:检查第三方库资源释放、限制单次分配大小、改用 mmap 预申请;
  • 图形内存:复用 GraphicBuffer、降低纹理分辨率、回收未使用的 Surface。
警告:不要在未确认故障树支路的情况下盲目修改堆大小参数(如 -Xmx),可能掩盖深层泄漏。

1.5 复测与验证

  1. 使用原触发场景重复操作,观察是否不再出现 OOM;
  2. 对比修复前后的 dumpsys meminfo 总内存曲线;
  3. 运行 monkey 测试 30 分钟,确认无崩溃。

1.6 预防措施

预防维度 具体做法
监控 集成 Profiler 或 MemoryAnalyzer 定期采集堆转储
代码审查 禁止在 UI 线程分配大对象,必须使用异步解码
自动化测试 CI 流水线中加入内存泄漏检测(LeakCanary)
限制资源 图形内存设置纹理池大小上限,超出则降级