macOS 内存模型深度解析 | x free 设计哲学

为什么 macOS 的内存这么复杂?

请添加图片描述

如果你用过 Linux 的 free 命令再看 macOS 的 vm_stat,会感到困惑——为什么 macOS 的内存统计如此混乱?wired、active、inactive、speculative、throttled、purgeable、occupied……这些术语让人头大。

根本原因在于 macOS 采用了复杂的内存压缩和分层缓存机制。

Linux 采用简单的 Swap(交换到磁盘)策略,而 macOS 为了兼顾性能和 SSD 寿命,引入了内存压缩器(Memory Compressor)——将不活跃的内存页压缩后保留在内存中,而非直接写入磁盘。

核心洞察:x-cmd 的恒等式模型

首先声明:这不是 macOS 官方文档中的模型,而是 x-cmd 团队原创的内存理解框架。

面对 macOS 复杂的内存统计,我们整理出了一个简洁的恒等式:

app + purgeable + cache = active + inactive + spec

这个等式的深刻含义:

  • Detail 层(左):从用途角度理解内存——应用数据、可清除缓存、文件缓存
  • vm_stat 层(右):从状态角度理解内存——活跃页、不活跃页、预读页

为什么这个恒等式很重要?

它建立了两种视角之间的数学映射关系。用户可以在 Detail 层理解"我的内存被用来做什么"(应用、缓存、可回收),同时知道这些内存对应于 vm_stat 层的哪些物理状态。

这个模型的价值:

  • 避免信息过载:从 vm_stat 的 10+ 个字段简化到 6 个概念
  • 保留核心 insight:哪些内存是用户数据、哪些可回收、哪些是系统保留
  • 可验证性:虽然存在 1-2% 的统计误差,但恒等式基本成立,证明模型是可靠的

Detail 层与 vm_stat 层:内存的双重表示

x free 的设计核心是双层表示:

Detail 层(概念抽象):wired  compressed  app  purgeable  cache  available
                           ↓         ↓      ↓        ↓       ↓         ↓
vm_stat 层(物理计数):wired   occupied    -        -       -   spec+throt+free

Detail 层:面向用户的概念视图

这一层回答**“内存被用来做什么”**:

字段含义UI 隐喻
wired内核保留内存(Pages wired down)🔴 红色 - 完全锁定,不可回收
compressed已压缩的数据🔴 红色 - 已占用,虽压缩但仍锁定
app应用工作集🔴 红色 - 活跃使用的应用内存
purgeable可清除缓存🟢 绿色 + 下划线 - 可回收标记
cache文件缓存🟢 绿色 + 下划线 - 可回收标记
available立即可分配🟢 绿色 - 真正的"空闲"

颜色设计的含义:

  • 🔴 红色 = 当前正在占用:表示这部分内存当前被系统或应用使用
  • 🟢 绿色 = 空闲或大概率可回收:purgeable、cache、available 以及 vm_stat 层的 free/spec/throt
  • 下划线 = 求和关系:purgeable + cache + app = active + inactive

关于 app 颜色的说明:
虽然理论上部分 app 内存(inactive)可以被压缩或 swap,但由于我们无法准确区分 active/inactive app(估算误差可能导致不等于 total),为了保守起见,app 被标记为红色,不计入 reusable。只有明确可回收的 purgeable 和 cache 才计入 reusable。

vm_stat 层:原始计数器视图

这一层展示底层 vm_stat 命令的原始计数器:

字段对应 Pages说明
wiredPages wired down同 Detail 层
compressedPages occupied by compressor压缩器占用的物理页
activePages active最近活跃的匿名页
inactivePages inactive不活跃但未被压缩的页
freePages free完全空闲
specPages speculative预分配页
throtPages throttled节流页(后台应用限制)

Detail 层字段详解

字段计算公式详细说明
hardwarehw.memsize - hw.memsize_usable硬件保留内存。被固件、GPU、EFI 等硬件永久占用的内存,操作系统无法管理或使用。在 128GiB 内存的 Mac 上,通常保留约 1.8-1.9 GiB
wiredPages wired down内核保留内存。包含内核代码、驱动、关键数据结构以及被 mlock() 锁定的用户内存。这部分内存不能被交换或压缩。系统内存压力时,wired 是唯一完全不可回收的部分
compressedPages occupied by compressor已压缩数据占用。被内存压缩器压缩后占用的物理页。虽压缩但仍处于占用状态,不能用于其他用途
appAnonymous pages - Pages purgeable应用工作集。应用实际使用的匿名内存(不包括可清除部分)。标记为红色因为无法准确区分可回收部分
purgeablePages purgeable可清除缓存。应用标记的可清除内存,系统内存压力时可被清除或换出。属于可回收内存
cacheFile-backed pages文件缓存。从文件映射的内存页,可被回收以释放内存。属于可回收内存
availablePages free + Pages throttled立即可分配。真正的空闲内存(不包含 speculative)。这是系统可立即分配给新请求的内存

为什么 Detail 层的 purgeable/cache 在 vm_stat 层没有直接对应?

因为它们散布在 active/inactive 中——macOS 的可清除内存是一种标记,而非独立的页类型。vm_stat 看到的是物理页状态,而 purgeable 是一种逻辑属性。

恒等式:数据一致性的数学表达

total ≈ wired + compressed + app + purgeable + cache + available
available = free + spec + throt
app + purgeable + cache = active + inactive  (Detail 层求和 = vm_stat 层求和)

最后一行就是 x free 中下划线标记的深意:通过视觉对齐和数学恒等式,建立 Detail 层和 vm_stat 层之间的映射关系。

设计哲学:为什么选择这些概念?

1. 定位:中间层设计

x free 的定位是:比图形界面(活动监视器)更专业,比 vm_stat 更易懂。

工具特点问题
活动监视器过于简化,只显示 App/联动/压缩/已使用信息不足,无法理解内存去向
vm_stat过于底层,全是 Pages 计数难以理解,概念抽象
x free中间层,既专业又可理解✅ 平衡专业性与可读性

2. 命名以 vm_stat 为锚点

命名原则:直接使用 vm_stat 术语,避免引入不必要的新概念。

x free 字段来源为什么不创造新术语?
wiredPages wired down直接使用,用户可对照 vm_stat
compressedPages occupied by compressor直接使用,清晰明确
freePages free直接使用,与系统一致
specPages speculative缩写但可识别

刻意避免的术语:

  • 不引入 Linux 的 buff/cache、shared 等概念(避免跨平台混淆)
  • 不创造脱离 vm_stat 的全新术语体系(保持可对照性)

3. Detail 层引入有限易懂概念

在 vm_stat 基础上,仅引入 3 个新概念:

新概念含义引入原因
reusable可回收内存(purgeable + cache + available)避免与 vm_stat 的 free 和 Detail 的 available 混淆
app应用工作集(Anonymous - purgeable)比 anonymous 更直观,避免 Linux anon 概念
cache文件缓存(File-backed pages)直观易懂,虽与 Linux 类似但实现不同

为什么不引入更多概念?

  • 每增加一个概念就增加认知负担
  • 目标是在"完整理解"和"易于理解"之间找到平衡点

4. 恒等式(约等)的价值

问题:为什么允许 1-2% 的误差,而不是精确相等?

答案:

  1. 物理现实:vm_stat 不是原子快照,各计数器采集有时间差
  2. 内核动态:采集期间内核可能在分配/回收内存
  3. 模型价值:尽管有约等,但恒等式帮助用户建立完整的内存概念模型
app + purgeable + cache ≈ active + inactive + spec

这个约等式通过下划线视觉提示,让用户理解:同一物理内存,在不同抽象层有不同表示。

设计取舍:

  • 精确性 vs 可理解性:选择后者
  • 完整性 vs 简洁性:通过分层(Mem/Detail/vm_stat)实现两者兼顾

设计原则总结

┌─────────────────────────────────────────────────────────┐
│  x free 设计原则                                         │
├─────────────────────────────────────────────────────────┤
│  1. 中间层定位:专业 < x free < 底层                     │
│  2. 命名锚定:直接使用 vm_stat 术语                      │
│  3. 有限概念:仅引入 3 个必要新概念                      │
│  4. 约等有价值:建立概念模型比精确更重要                 │
│  5. 视觉隐喻:颜色 + 下划线 = 信息密度最大化             │
└─────────────────────────────────────────────────────────┘

设计演进:从 Identity 验证失败到 Hardware 列的诞生

这不是误差,而是一个设计故事的缩影。

在 x free 早期版本中,我们依赖一个核心恒等式验证数据正确性:

app + purgeable + cache = active + inactive

在 32GiB 的 Mac 上,这个等式看起来"基本成立"——误差只有几百 MiB,在可接受范围内,我们认为是统计时序差异或内核动态分配导致的。

但当我们把代码部署到 128GiB 的 Mac 上时,问题暴露无遗:

  • vm_stat 所有计数器加起来只有 ~126GiB
  • 少了整整 1.9GiB(不是几百 MiB!)
  • 如果这是统计误差,那比例高达 1.5%,完全不可接受

排查过程:

  1. 怀疑计算错误:检查 AWK 脚本,确认没有溢出或舍入问题
  2. 怀疑 vm_stat 遗漏:对比 vm_stat 和 sysctl hw.memsize,发现后者确实是 128GiB
  3. 关键发现:sysctl hw.memsize_usable 返回 126.1GiB
  4. 真相大白:hw.memsize - hw.memsize_usable = 1.9GiB,这正是缺失的部分

什么是 Hardware 保留内存?

hw.memsize          : 128.0 GiB  (硬件总内存)
hw.memsize_usable   : 126.1 GiB  (操作系统可用内存)
hardware 保留      :   1.9 GiB  (固件/GPU/EFI 占用)

这部分内存去哪了?

  • 固件代码:EFI/UEFI 启动时加载的固件保留区域
  • GPU 显存映射:集成显卡或独立显卡预留的内存映射区域
  • 系统管理区域:Apple Silicon 的 Secure Enclave 等安全区域
  • 内存对齐损耗:大页内存管理的对齐开销

为什么 32GiB 机器上不明显?

  • 32GiB 机器保留约 0.8GiB(2.5%)
  • 128GiB 机器保留约 1.9GiB(1.5%)
  • 绝对值差异大(0.8 vs 1.9),但比例相近
  • 在 32GiB 上 0.8GiB 容易被当作"内核开销"忽略

设计决策:增加 hardware 列

这个发现促使我们在 UI 中增加了 hardware 列:

  • 颜色:暗淡红色(Dim Red)——表示"永久占用,OS 无法管理"
  • 位置:最左列——表示"从 total 中首先扣除"
  • 目的:解释为什么 vm_stat 看不到全部内存

这就是 x free 的设计哲学:不是掩盖复杂性,而是清晰地展示复杂性。

wired:特殊的内核内存

wired 内存的特殊性:

类型可 swap可 compressed说明
wired❌ NO❌ NO内核锁定,完全不可回收
compressed❌ NO✅ 已压缩已在内存中压缩
app⚠️ 部分可以✅ 部分可以inactive 部分可回收,但无法准确区分
purgeable✅ 可以✅ 可以随时可被清除
cache✅ 可以✅ 可以文件缓存
availableN/AN/A本来就是空闲的

颜色说明:红色仅表示"当前正在占用",不代表完全不可回收。但 wired 确实是唯一完全不可回收的,而 app 由于无法准确估算可回收部分,保守起见标记为红色且不计入 reusable。

UI 设计的哲学

x free 的界面设计遵循**“认知分层”**原则:

  1. 首行 Mem/Swap:最高概览,回答"内存够不够用"
  2. Detail 层:概念抽象,回答"内存被用来做什么"
  3. vm_stat 层:原始细节,回答"底层究竟怎么统计的"

缩进和空行的意义:

  • 2列缩进:将内容从行首"推入",视觉上形成统一的阅读区块
  • 空行分隔:在 -c 重复模式下,每次输出之间有足够的视觉间隙

下划线(underline)而非反色(reverse video):

  • 反色过于突兀,会抢夺注意力
  • 下划线温和地提示"这是一个有数学关系的分组"

从 vm_stat 到 Detail:信息重编码

原始 vm_stat 输出:

Pages wired down: 823142
Pages occupied by compressor: 456789
Pages active: 1234567
Pages inactive: 987654
Pages speculative: 12345
Pages throttled: 0
Pages free: 567890

经过 x free 重编码后:

wired  compressed  app       purgeable  cache   available
3.2 Gi  10.4 Gi     10.7 Gi   341 Mi     5.5 Gi  837.5 Mi

重编码的价值:

  • 从"页计数"到"人类可读单位"
  • 从"原始状态"到"概念用途"
  • 从"孤立数字"到"颜色编码的系统"

这就是 x free 的设计哲学:让复杂的 macOS 内存模型变得可理解、可推理、可决策。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐