11.2 内存管理与虚拟内存

一句话

虚拟内存把 “地址” 与 “物理页” 解耦,代价是缺页与 TLB;你看到的 RSS 只是冰山一角——page cache、共享库、匿名页、内核结构都在争同一批物理页,OOM 只是最后一个结果。

地址转换链路

虚拟地址 → TLB 查(命中 ~1ns)→ 未命中查页表(4 级,~10-100ns)
        → 页不存在 → 缺页异常(minor:映射已有页;major:读磁盘,~100μs-10ms)
概念 含义 观察
页大小 通常 4KB;大页 2MB / 1GB getconf PAGE_SIZE
缺页 minor(映射)/ major(IO) /proc/vmstat 的 pgmajfault
预读 内核按顺序预测多读几页 readahead
反向映射 从物理页找回所有映射它的进程 影响回收成本

分配器:用户态与内核态各一层

层 机制 关注点
用户态 brk(小)、mmap(大)、glibc arena 碎片与多线程争用
内核态 buddy + slab/slub 内存碎片、分配延迟
语言运行时 Go 的 mspan/mcache、JVM 的分代 见 15.3 内存分配与逃逸分析

四个常见指标的含义

指标 含义 误读
VSZ 虚拟地址空间大小 大不代表占内存(可能未映射页)
RSS 实际驻留物理页 含共享库与 page cache 部分
page cache 文件缓存 可以被回收,不算 “泄漏”
swap 换出的匿名页 用上 swap 往往意味着延迟灾难

落到数字:缺页与 TLB

事件 量级 触发条件
TLB 命中 ~1ns 页表项在 TLB 中
TLB 未命中(页表命中) 10–100ns 4 级页表遍历
minor fault 1–10μs 页已缓存但未映射
major fault 100μs–10ms 需要读磁盘/网络存储
2MB 大页 页表项减少 512× 随机访问密集时收益明显
cat /proc/meminfo | grep -E "MemAvailable|Cached|SwapUsed" 2>/dev/null || vm_stat | head -8
grep -E "^(Rss|Pss|Anonymous|Private_Dirty)" /proc/self/smaps_rollup 2>/dev/null || echo "macOS 无 smaps"

反模式

反模式 后果
用 RSS 判断泄漏 page cache 波动被误判为泄漏
关闭 swap 却按 “有 swap” 规划容量 内存压力时直接 OOM Kill
在容器里按宿主机内存设堆上限 超过 cgroup 限制被杀(见 6.3 namespace (隔离) 与 cgroups (限制))
盲目开透明大页 分配延迟抖动(THP 的 compaction)
忽略 GOMEMLIMIT/堆上限 GC 目标失控,RSS 一路涨

三条铁律

🔑 RSS ≠ 内存需求:page cache 与共享库都在里面。

🔑 major page fault 才是磁盘 IO 的信号;minor 只是映射。

🔑 容器内存上限要同时约束 “堆 + 栈 + page cache”,只调堆没用。

自检

Q1 服务的 RSS 从 1GB 涨到 2GB,但 GC 日志显示堆稳定,最可能是什么?

▸ 答案:page cache 或堆外内存:文件读写、mmap、网络缓冲区都会让 RSS 上涨,这部分可回收,不一定是泄漏。区分方法:看 smaps_rollup 的 Pss 与 Anonymous、File-backed 拆分;再看 GC 后的 RSS 是否回落。

Q2 为什么 major page fault 会让延迟出现 “毫秒级尖刺”?

▸ 答案:因为 major fault 需要读磁盘(或网络存储):内核要分配页、发起 IO、等待返回、更新页表,期间进程被阻塞。SSD 上约 100μs,机械盘或网络存储可达毫秒级。预热、预读与内存充足都能减少它。

Q3 VSZ 很大(比如 20GB)是问题吗?

▸ 答案:通常不是。VSZ 包含未实际映射的地址空间(如 mmap 预留、线程栈预留、Go 的堆保留)。判断要看 RSS 与 Anonymous。但如果 VSZ 接近系统 vm.max_map_count 或地址空间上限,会分配失败。

Q4 容器里 JVM/Go 的堆上限该怎么设?

▸ 答案:按 cgroup 限制设,而不是按宿主机内存:JVM 用 -XX:MaxRAMPercentage(配合 UseContainerSupport),Go 用 GOMEMLIMIT(见 15.4 GC 原理与调优 (GOGC / GOMEMLIMIT))。要给 page cache、栈、堆外留 20%–30%,否则 “刚好用满” 时会被 OOM Kill。

Q5 什么时候该用大页(HugePages)?

▸ 答案:大内存 + 随机访问密集的场景(数据库缓冲池、DPDK 网络栈):2MB 页把页表项减少 512 倍、TLB 命中率提升,随机访问延迟下降。代价是分配需预保留、碎片与运维复杂;普通 Web 服务收益不明显。

Q6 为什么 MemAvailable 比 MemFree 更适合判断 “还有多少内存可用”?

▸ 答案:因为 MemFree 只统计完全空闲的页,而 page cache 里的干净页可被立即回收。MemAvailable 已经把 “可回收的缓存与 slab” 算进来,更接近 “新进程还能拿到多少内存”。

关联