内存管理优化 · 方案详解

十个优化方向,每个方向配齐六件套:现状流程图 → 逐行源码证据 → 优化空间量化 → 方案流程图 → 选型对比 → 风险与落地。源码取自 XNU 与 Linux 官方存档(卡头标注版本与行号,可机械复核);讲解先说人话、再上术语。

XNU:xnu-7195.141.2(iOS 14.8,六版本演化存档 iOS 12→18)
Linux:5.10 / 6.1 / 6.6 / 6.12(Android 16)/ 6.18(Android 17)/ master(7.3-rc3)
全部结论附行号锚点 · 2026-09

目标 B · 冷热页管理有效性

把冷的找对、把热的护住、把冷的放对地方。三个子问题:判定(谁冷谁热,C1/C3/C6)、保护(热集不误伤,C2)、处置(声明 / 分级放置 / 召回,C4/C5/C7)。
为什么重要:判定错,压缩/换出/重压全链路白费;热集误伤一次就是用户可感知的卡顿。

目标 A · 内存管理性能

内存管理路径自身跑多快、多省:吞吐(大 folio 批量压缩/换出,P1)、CPU 成本(按页择优压缩,P2)、延迟(冷页召回,P3)。
为什么重要:吞吐是冷处置的预算上限——路径越快,压力期越从容,越不需要 kill 止血;CPU 是隐性性能税,按百万页放大。

选向标准(以及为什么有些方向没入选)

直接服务两个目标——记账口径(zram 池归属)、kill 归因类议题服务的是「限额与问责」,不服务性能与冷热有效性,明确不入选;② XNU 已证明价值——Android 17 正沿同一路径收敛(mmd + memory-limiter),方向被产业验证;③ 机制骨架已备——多数方向可从用户态起步,不必等上游。

反向视角:Linux 在 PSI / MGLRU / DAMON / 可配置性上已经领先 XNU(C6 即 Linux 领先项的盘活)。总原则:学 XNU 的语义,用 Linux 的机制表达——不做整体搬入式移植,不推翻 Linux 十年建立的机制 / 策略分层。

十个方向索引

优先级 ★ = 立即试点价值 / 成本比(★★★ = 最推荐先行;基于厂商视角的收益、成本、风险综合判断)。

术语速查(按出现顺序)

冷页 / 热页

按访问频率区分:长时间没人访问的是冷页,反复被访问的是热页。冷热管理的全部意义:冷的腾地方,热的别动。

MGLRU 世代 (generation)

把页按「最近一次被访问的时间段」分成若干代:最新访问的在 max_gen(最热),最久没碰的在 min_gen(最冷)。MGLRU = Multi-Gen LRU,Linux 6.1 起默认的下一代 LRU。

min_ttl_ms

「最近 N 毫秒内访问过的页不许逐出」的保护窗口,写在 /sys/kernel/mm/lru_gen/min_ttl_ms——目前全局一个值。

refault(回弹)

被逐出的页很快又被访问(缺页读回)。refault 率高 = 逐错了 = 用户感到卡顿。refault 距离 (R−E):内核算出的「这个页值不值得留」的判定数。

zram

用一块 RAM 当压缩池做 swap 的块设备。冷页压缩后仍留在 RAM:召回快,但省得有限。

writeback(回写)

把 zram 里的冷页写到真实存储(NAND)——省 RAM 到极致,代价是召回变成毫秒级 IO,且消耗闪存寿命(有日预算)。

recompression(重压)

对 zram 里已压缩的页用更狠的算法(如 zstd)再压一遍——省 RAM,代价是 CPU。

c_segment(段)

XNU 压缩器的基本打包单位:一段装 N 页的变长槽。元数据 / 搬移 / 换出 / 年龄统计都以段为单位——对照 zram 的逐页模型。

purgeable / volatile

应用主动声明「这块内存可丢弃,需要时我再生成」的机制(iOS 全生态使用)。XNU 用 token 队列精确记账可丢页数。

工作集 (working set)

保持流畅运行必须驻留内存的页集合。低于工作集 = 卡顿;保护工作集 = 保护流畅。

PSI

Pressure Stall Information——「等内存等了多久」的精确度量。它回答「有多堵」,不回答「该不该逐」。

DAMON / DAMOS

Linux 的数据访问监控框架:按「区域」自适应采样(而非逐页),DAMOS 是基于它的操作方案。Pixel 8 GKI 已启用。

流程图图例

起点 / 终点
处理步骤
判定 / 条件
🔴 痛点(现状问题)
✚ 新增 / 改造(方案)
存储 / IO 出口
╌▶反馈环(红虚线)
灰色斜体 = 说明

落地路线图 · 依赖关系 · 度量基线

三阶段 × 谁挡着谁 × 怎么证明有效
顺序原则:先用户态(零内核改动,先攒观测数据)→ 再产品内核补丁(判定 / 保护 / 放置)→ 最后框架级上游(语义 / API)。每个阶段都产出可度量的收益,不是纯研究。

三阶段

短期 0~6 月 · 用户态 / 配置(零内核改动)
  • 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 已开启(很多设备根本没开)。
依赖:Android 14 GKI(6.1)起 · 收益:存量设备获得 Android 17 的主要内存收益;反馈数据开始积累
中期 6~18 月 · 产品内核补丁
  • 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 上游化。
依赖:GKI vendor hook 或上游化 · 收益:冷判定质量、热集保护、放置正确性、召回延迟
长期 · 框架级上游
  • C5 volatile API:memfd ioctl + 图形栈(gralloc / dmabuf heap)接入——SurfaceFlinger buffer cache 是最大受益者。
  • P1 zram 大 folio 压缩:等 RFC 重启,或厂商先行试点再反哺上游。
  • C6 DAMOS 编排产业化:ART heap / 浏览器的区域级处置。
依赖:主线 mm 社区共识 · 收益:语义级对齐 XNU,生态级收益

依赖关系(谁挡着谁)

方向前置依赖下游受益
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 的激进处置空间

读法:C3 和 C1 是「公共底座」——先做观测(C3 前半)再上温度(C1),是全局最优路径;C5 / C6 / P1 无前置,随时可并行启动。

怎么证明有效(度量基线)

方向首选度量验收基线(示例)
C1 / C2 / C3per-memcg refault 率(file/anon 分路)、PSI some满载场景 refault 率 ↓ ≥ 20%,PSI some 有可测下降
C4冻结 app 内存驻留(memory.current)、thaw 首帧时间驻留 ↓ ≥ 50%;thaw 首帧 < 800ms
C5实际丢弃量、通知后重生成的 CPU丢弃量 > 0 且无前台卡顿回归
C6DAMON 采样 CPU 占用、区域处置时延采样 ≤ 1% CPU
C7NAND 日写入量 vs 预算、zram 同页重入率日写入 ≤ 预算(mmd 为 24GiB/日级)
P1压缩吞吐(页 / 秒)、per-page CPU吞吐 ≥ 2×
P2首次压缩 CPU 均值、整体压缩比CPU ↓ ≥ 15%,压缩比不降
P3换入 P99 延迟、thaw 总时长P99 ↓ ≥ 30%

达标线为示例基线(首发试点的验收建议),非上游承诺;每个试点应同时跑对照组(同机型同负载不开特性),用同一套 tracepoint 取数。