Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第 10 章:内存性能

内存问题影响体验的方式很多:OOM(内存不足)、频繁 GC、帧丢失、前后台恢复变慢、系统换页、cached process(缓存进程)重启,以及业务运行越久越迟缓。本章从应用性能视角组织诊断方法,重点回答三个问题:

  1. 增长发生在哪类内存中;
  2. 谁持有对象或资源;
  3. 这项变化怎样影响 GC、调度、I/O 和用户可感知延迟。

从增长、持有到体验影响

图:三个问题互相补充:分类指标定位增长范围,所有权证据说明谁仍持有资源,同一业务窗口的时间线检验体验影响。

源码基线:Android 17 / API 37 / AOSP android-17.0.0_r1;涉及 PSI(资源压力导致任务停顿的时间)、cgroup v2(控制组机制)、回收与内核内存统计时,内核核对版本为 android17-6.18-2026-06_r6;旧版本差异保留在对应专题中,结论范围不超过 Android 17。

内容索引

四篇专题按现场问题选读

图:10.1 提供 App 排障主线;按低内存、分配抖动或图形资源线索进入 10.2–10.4。专题之间可以交叉取证,不是固定执行顺序。

按现象选择入口

现场现象建议入口
不清楚 Java、Native、PSS、RSS、SwapPss、Graphics 的区别10.1 App 内存分析与案例
页面退出后 Activity、View、callback 或资源仍被持有23.2 内存泄漏检测与治理
多轮业务操作后内存持续增长10.1 App 内存分析与案例
PSI、换页、lmkd、后台重启或整机卡顿10.2 低内存对系统性能的影响
需要查看从症状到证据的完整案例10.1 App 内存分析与案例
live set(仍存活的对象集合)稳定,但短命分配和 GC 很频繁10.3 内存抖动与频繁 GC
Graphics、DMA-BUF(跨设备共享的缓冲区)或 GPU private memory(GPU 私有内存)增长10.4 GPU 与图形内存统计、归因与诊断
SQLite/Room 查询、CursorWindow 或分页造成峰值24.2 数据库与序列化性能
ART region、CMC(并发标记压缩)、LOS(大对象空间)或 compaction(内存压缩整理)引起疑问4.2 ART Heap、GC 与后台维护调度

分析顺序

拿到一个内存问题,我们按固定的顺序推进:先定义业务窗口,再确定内存域,然后区分 live set、churn 与峰值,最后做单变量对照实验。

1. 定义业务窗口

我们先把问题写成一段可重复的操作序列,再记录现场条件:设备、build fingerprint(系统构建标识)、ART Mainline(可独立更新的 ART 系统模块)版本、应用版本、ABI(应用二进制接口)、构建类型、数据集、前后台状态和采样时钟。

先固定现场条件,再对齐阶段

图:先定义可重复操作与等待区间,保存设备、构建、ART 模块、应用、ABI、负载和前后台状态。轨道仅示意按共同时间基准对齐;不同统计值不因此变成同一计量口径。

2. 确定内存域

窗口固定后,接下来确定增长落在哪个内存域。我们至少从以下视角调查;这些视角互相交叉,统计时按域分别记录与比较。其中线程和文件描述符是资源线索,PSI 衡量资源压力造成的任务停顿,属于时间维度的压力信号(见 PSI 官方说明):

  • ART managed heap(由 ART 管理的 Java/Kotlin 对象堆);
  • Native heap;
  • 匿名映射(没有文件作为后备存储)与文件映射;
  • Graphics、DMA-BUF 与 GPU private memory;
  • Stack(线程栈)、Code(代码映射)、线程和文件描述符;
  • Swap/ZRAM(交换区与压缩内存)和系统 PSI。

容量、资源与压力分别解释

图:这些是交叉的调查视角:ART 与 Native 堆可由匿名映射承载,图形资源也可能进入其他统计;线程数、FD 数和 PSI 不是另一套堆字节数。

视角确定之后,还要弄清各域的回收责任。GC 主要回收 managed heap 中不可达对象占用的堆空间。部分 Java 对象关联的 Native 分配通过 NativeAllocationRegistry 登记,对象不可达后由清理机制调用登记的释放函数(见 固定 r1 源码)。这条清理路径只覆盖登记过的分配,所以其余的 Native、Surface、codec(编解码器)、GPU 或内核资源,要逐类核对 owner(资源持有者)、关闭责任、共享引用和异步工作完成条件。

Java 可达性可以关联已登记 Native 清理

图:部分 Native 分配登记了与 Java 对象关联的释放函数;对象不可达后可经清理机制触发释放。图中不承诺执行时机,也不表示所有 Native 或 GPU backing 已同步回收。

3. 区分 live set、churn 与峰值

确定内存域之后,我们用不同工具区分 live set、churn 与峰值:

  • heap dump(堆转储)用于分析转储中的对象与引用链;记录采集工具是否先触发 GC 及其对象过滤策略,转储也会扰动进程内存(见 官方工具说明);
  • ART allocation profile(分配剖析)用于分析分配调用栈与分配速率;
  • Native heap profile 用于分析 malloc/free;
  • Perfetto 用于对齐 GC、调度、frame(帧)、fault(缺页)、PSI 与业务阶段;
  • dumpsys meminfo 和 smaps(进程内存映射明细)用于确定统计分类与映射归属;
  • DMA-BUF、memtrack 和 GpuService 分别提供图形对象、分类或驱动上报证据;先确认设备支持、权限、覆盖范围与采集完整性,缺的数据按未知处理,不计成 0。

工具标签能把调查范围缩小到具体的内存域和分配路径;要提出可验证的修复方案,我们还要拿到 owner、对象生命周期或调用栈。

按问题选择工具,区分创建与持有

图:heap dump 查对象引用;分配剖析查创建热点和速率;时间线检验体验相关性;映射与图形统计限定增长范围。工具提供的证据不同,创建栈不能代替保留引用图。

4. 做单变量对照实验

我们用单变量对照实验检验调整的效果:一次只调整一个变量,例如缓存容量、图片目标尺寸、任务并发数、解析批次或 buffer(缓冲区)规格。比较内存指标时,同时观察 CPU、I/O、GC、frame deadline miss(帧没有按期完成)、缓存命中率和业务尾延迟(少数最慢请求的耗时)。

如果内存降下来了,但重建成本、I/O 或卡顿明显增加,方案就还需要继续调整。

单变量实验同时观察收益与代价

图:保留同一负载和现场条件,一次调整一个变量;同时比较内存、CPU、I/O、GC、帧超期、命中率与尾延迟。根据证据选择保留或继续调整,不预设缓存越小越好。

阅读建议

入口表按现象分流,这里再补充几条跨专题的读法:

  • App 侧排障从 10.1 开始,再按内存域进入对应小节。
  • OOM、GC 抖动和前后台恢复问题,建议与第 4 章的 ART、lmkd、MemoryLimiter、内核回收章节对照。
  • 图形与渲染问题要把 10.4 和渲染专题放在同一时间线分析,避免只看 Java heap。
  • 平台或 OEM(设备厂商)调试还应保存 cgroup、PSI、lmkd(低内存终止守护进程)、MemoryLimiter 与 ART 配置,设备实际状态以这些现场配置为准,API level 说明的是框架版本。

证据边界

全章的结论,我们按这几条标准把关:

  • 数据要有源码路径、官方文档或可复现实验支撑。
  • PSS、RSS、Java heap、Native allocated 和 GPU memory 维持各自的统计范围,合计之前先核对口径。
  • System.gc()、WeakReference、对象池、WebP、SparseArray、异步线程的讨论都限定在具体场景,是否适用要看场景条件。
  • 证明泄漏要靠重复观察,一次快照不够;确认资源已经释放,一次 GC 之后仍要继续观察。
  • 性能收益必须注明设备、构建、负载、指标和对照条件。

结论保留口径、重复性与对照条件

图:快照、GC 和工具标签都只是一段证据。泄漏与释放结论需结合重复业务、持有关系及资源生命周期;性能收益需保存可复查的现场条件。