内存管理优化 · 方案详解
二十个优化方向,每个方向配齐六件套:现状流程图 → 逐行源码证据 → 优化空间量化 → 方案流程图 → 选型对比 → 风险与落地。源码取自 XNU 与 Linux 官方存档(卡头标注版本与行号,可机械复核);讲解先说人话、再上术语。
Linux:5.10 / 6.1 / 6.6 / 6.12(Android 16)/ 6.18(Android 17)/ master(7.3-rc3)
Android 用户态:AOSP lmkd(Android 14 存档)· 全部结论附行号锚点 · 2026-09
目标 B · 冷热页管理有效性
把冷的找对、把热的护住、把冷的放对地方。四个子问题:判定(谁冷谁热,C1/C3/C6/C12)、保护(热集不误伤,C2)、处置(声明 / 分级放置 / 召回,C4/C5/C7/C10)、覆盖面(决策证据 / 时机 / slab / 容量 / 共享页,C8/C9/C11/C13/C14)。
为什么重要:判定错,压缩/换出/重压全链路白费;热集误伤一次就是用户可感知的卡顿。
目标 A · 内存管理性能
内存管理路径自身跑多快、多省:吞吐(大 folio 批量压缩/换出,P1)、CPU 成本(按页择优压缩 + 卸载出前台路径,P2/P6)、延迟(冷页召回,P3)、编排与鲁棒(异步批量接口 / 压力期定向解救,P4/P5)。
为什么重要:吞吐是冷处置的预算上限——路径越快,压力期越从容,越不需要 kill 止血;CPU 是隐性性能税,按百万页放大。
✦选向标准(以及为什么有些方向没入选)
① 直接服务两个目标——记账口径(zram 池归属)、kill 归因类议题服务的是「限额与问责」,不服务性能与冷热有效性,明确不入选;② XNU 已证明价值——Android 17 正沿同一路径收敛(mmd + memory-limiter),方向被产业验证;③ 机制骨架已备——多数方向可从用户态起步,不必等上游。
反向视角:Linux 在 PSI / MGLRU / DAMON / 可配置性上已经领先 XNU(C6 即 Linux 领先项的盘活)。总原则:学 XNU 的语义,用 Linux 的机制表达——不做整体搬入式移植,不推翻 Linux 十年建立的机制 / 策略分层。
✦二十个方向索引
优先级 ★ = 立即试点价值 / 成本比(★★★ = 最推荐先行;基于厂商视角的收益、成本、风险综合判断)。
✦术语速查(按出现顺序)
按访问频率区分:长时间没人访问的是冷页,反复被访问的是热页。冷热管理的全部意义:冷的腾地方,热的别动。
把页按「最近一次被访问的时间段」分成若干代:最新访问的在 max_gen(最热),最久没碰的在 min_gen(最冷)。MGLRU = Multi-Gen LRU,Linux 6.1 起默认的下一代 LRU。
「最近 N 毫秒内访问过的页不许逐出」的保护窗口,写在 /sys/kernel/mm/lru_gen/min_ttl_ms——目前全局一个值。
被逐出的页很快又被访问(缺页读回)。refault 率高 = 逐错了 = 用户感到卡顿。refault 距离 (R−E):内核算出的「这个页值不值得留」的判定数。
用一块 RAM 当压缩池做 swap 的块设备。冷页压缩后仍留在 RAM:召回快,但省得有限。
把 zram 里的冷页写到真实存储(NAND)——省 RAM 到极致,代价是召回变成毫秒级 IO,且消耗闪存寿命(有日预算)。
对 zram 里已压缩的页用更狠的算法(如 zstd)再压一遍——省 RAM,代价是 CPU。
XNU 压缩器的基本打包单位:一段装 N 页的变长槽。元数据 / 搬移 / 换出 / 年龄统计都以段为单位——对照 zram 的逐页模型。
应用主动声明「这块内存可丢弃,需要时我再生成」的机制(iOS 全生态使用)。XNU 用 token 队列精确记账可丢页数。
保持流畅运行必须驻留内存的页集合。低于工作集 = 卡顿;保护工作集 = 保护流畅。
Pressure Stall Information——「等内存等了多久」的精确度量。它回答「有多堵」,不回答「该不该逐」。
Linux 的数据访问监控框架:按「区域」自适应采样(而非逐页),DAMOS 是基于它的操作方案。Pixel 8 GKI 已启用。
透明大页:多个 4K 连续页拼成一个大页(映射为一),省 TLB、省 IO 次数。mTHP 是多尺寸版(64K/128K…)。代价:换出 / 压缩按整页算——见 C12。
内核小对象(inode/dentry…)的池子叫 slab;每个池子注册 shrinker 回调(count 报库存 / scan 兑现回收)。语义各家一套——见 C11。
XNU 的进程优先级带(0~210):IDLE→BACKGROUND→FREEZER(75)→FG(100)→CRITICAL(190)。内存不够时从低 band 往高 band 杀进程。
Android 的用户态内存治理进程(low memory killer daemon):按 PSI / thrashing / oom_adj 排序杀进程。输入全是压力信号——见 C8。
Android 图形 buffer 的内核分配接口(system / CMA heap)。6.12 的 system heap 是 buddy 裸分配器,无冷处理语义——见 C10。
✦流程图图例
落地路线图 · 依赖关系 · 度量基线
①三阶段
- C4(a) 存量回移:Android 14~16 复刻 mmd 管线——cgroup v2 freeze + memory.reclaim(6.1+ 可用;swappiness= 选项需 6.12+)+ 全局 mark_idle / writeback。
- C3 前半:回读率 / refault 观测——tracepoint + debugfs 直方图,先攒 1~2 个版本周期的数据,再谈稳定接口。
- MGLRU 调优:min_ttl_ms 真正用起来;确认 same-filled / recompression 已开启(很多设备根本没开)。
- C9 用户态起步:息屏广播驱动的处置窗口(memory.reclaim + mark_idle / writeback 批处理,亮屏即中止)——与 C4(a) 同批零内核改动;C14 观测:memory.stat 补 shared 统计先跑起来。
- C1 世代年龄接入:TRACK_ENTRY_ACTIME + MGLRU 世代时间戳 → slot 温度(C2-C7 / P2 / P3 的公共输入)。
- C2 工作集 floor:min_ttl_ms per-memcg 化 + refault 反馈调参。
- C7 放置策略层:三输入决策(压缩收益 × 召回概率 × 闪存成本)。
- C3 后半:PSI 风格稳定接口 · P3:zram 大 folio 换入 · C4(c):per-process writeback ioctl 上游化。
- C8 处置证据:per-memcg 处置状态导出(tracepoint)→ LMKD kill 排序输入 + thaw 豁免窗口。
- C11 / C14:list_lru per-memcg 记账完善 + shared 计数接口(批量 + 缓存)。
- P6 压缩卸载:压缩请求入队 + worker 池(小核亲和),紧急同步兜底。
- C13 zram 弹性:slot 元数据分段懒分配 + 扩容路径(vendor 分支试点)。
- C5 volatile API:memfd ioctl + 图形栈(gralloc / dmabuf heap)接入——SurfaceFlinger buffer cache 是最大受益者。
- P1 zram 大 folio 压缩:等 RFC 重启,或厂商先行试点再反哺上游。
- C6 DAMOS 编排产业化:ART heap / 浏览器的区域级处置。
- C10 图形 purgeable:dma-buf heap volatile 语义 + 记账即时切换(与 C5 同一套 ioctl)。
- P4 异步接口:memory.reclaim_async(提交即返回 + 完成通知 + 批量 memcg)。
- P5 定向解救:vmscan 三态流控(检测 → 解救 → 观察),参数反馈化。
- C12 温度驱动 THP:khugepaged / deferred split 接世代年龄(vendor hook 试点)。
②依赖关系(谁挡着谁)
| 方向 | 前置依赖 | 下游受益 |
|---|---|---|
| C1 温度 | C3(观测数据先攒 1~2 个版本周期) | C4 / C7 / P2 / P3——温度是它们的判定输入 |
| C2 保护 | C3(refault 反馈调参) | 全部冷处置方向的「安全网」 |
| C3 反馈 | 无(tracepoint 观测可立即做) | C1 / C2 的自动调参 |
| C4 冻结处置 | C1(替代 2h~4h 空闲时钟) | P3(thaw 召回风暴对冲) |
| C5 声明式冷页 | 无 | 图形栈(buffer cache 可再生页) |
| C6 DAMON | 无(Pixel 8 GKI 配置已启用) | ART heap、浏览器大地址空间 |
| C7 放置 | C1(召回概率输入) | 闪存寿命预算管理 |
| P1 批量压缩 | 无(可等上游 RFC 或厂商先行) | C4 / C7 的 writeback 吞吐 |
| P2 择优压缩 | C1(只重压「将长期冷」的页才划算) | 前台 CPU 余量 |
| P3 召回延迟 | C1(召回窗口预测) | C4 / C7 的激进处置空间 |
| C8 kill 证据 | C4 / C1 / C3(处置证据与温度) | 无下游——冷处置投资的「回报出口」 |
| C9 窗口调度 | 无(用户态起步) | C4(窗口编排)、P3(resume 预热联动) |
| C10 图形冷处置 | C5(同一套 volatile 语义) | 图形栈 buffer cache(最大可再生品类) |
| C11 slab 回收 | C3(压力档位接口) | C8(zone 耗尽 kill 语义化) |
| C12 THP 温度 | C1(温度输入) | C7(拆出页放置)、P1(整页热时批量) |
| C13 zram 弹性 | C1 / C7(供给预测 + 腾空放置) | P4(异步伸缩操作) |
| C14 共享页去重 | C4 / C1(冻结管线 + 世代扫描) | C8(低私有占比 kill 排序) |
| P4 异步接口 | 无 | C4 / C9 / C13——用户态编排基础设施 |
| P5 流控解救 | C3(无进展原因观测) | C8(解救阶梯)、C2(偏置不击穿 floor) |
| P6 压缩卸载 | P2(worker 上跑择优) | P1(批量 folio 使 worker 更饱满) |
读法:C3 和 C1 是「公共底座」——先做观测(C3 前半)再上温度(C1),是全局最优路径;C5 / C6 / P1 / C9 / P4 无前置,随时可并行启动。
③怎么证明有效(度量基线)
| 方向 | 首选度量 | 验收基线(示例) |
|---|---|---|
| C1 / C2 / C3 | per-memcg refault 率(file/anon 分路)、PSI some | 满载场景 refault 率 ↓ ≥ 20%,PSI some 有可测下降 |
| C4 | 冻结 app 内存驻留(memory.current)、thaw 首帧时间 | 驻留 ↓ ≥ 50%;thaw 首帧 < 800ms |
| C5 | 实际丢弃量、通知后重生成的 CPU | 丢弃量 > 0 且无前台卡顿回归 |
| C6 | DAMON 采样 CPU 占用、区域处置时延 | 采样 ≤ 1% CPU |
| C7 | NAND 日写入量 vs 预算、zram 同页重入率 | 日写入 ≤ 预算(mmd 为 24GiB/日级) |
| P1 | 压缩吞吐(页 / 秒)、per-page CPU | 吞吐 ≥ 2× |
| P2 | 首次压缩 CPU 均值、整体压缩比 | CPU ↓ ≥ 15%,压缩比不降 |
| P3 | 换入 P99 延迟、thaw 总时长 | P99 ↓ ≥ 30% |
| C8 | 避免的 kill 数(灰度对照组)、thaw 后豁免命中率 | 每 BLE / 重载会话避免 kill ≥ 1 次 |
| C9 | 息屏窗口内 writeback / 重压占比、前台卡顿回归 | 窗口内重处置 ≥ 80%,前台无回归 |
| C10 | 丢弃量、footprint 阶跃、重生 CPU | 阶跃对齐 XNU 量级(真机 768MB) |
| C11 | slab 回收时延 P99(触发 → 释放) | 压力早期参与、末期不雪崩 |
| C12 | 折叠-拆分振荡率、回收期拆分量 | 振荡率趋 0,回收期拆分 ↓ |
| C13 | 容量错配时长、元数据占用 | 低负载设备元数据 ↓ ≥ 30% |
| C14 | 同物理页重复处置率 | 重复处置率 ↓ ≥ 50% |
| P4 | 提交 → 返回延迟、完成通知到达率 | 提交即返回(< 1ms) |
| P5 | 无进展周期数、解救成功率 | 死锁自解除时延 ↓ ≥ 50% |
| P6 | 前台线程压缩时间占比 | 前台压缩 CPU 占比 → ~0 |
达标线为示例基线(首发试点的验收建议),非上游承诺;每个试点应同时跑对照组(同机型同负载不开特性),用同一套 tracepoint 取数。