malloc仅分配虚拟内存,物理页在首次读写触发缺页异常时才由伙伴系统分配;若空闲页不足,则在page fault阶段立即启动内存回收。

内存分配:从 malloc 到物理页框,中间发生了什么
Linux 进程调用 malloc 申请内存时,实际只在虚拟地址空间划出一块区域,**并不立即分配物理内存**——这是 lazy allocation(惰性分配)的核心。真正触发物理页分配的,是第一次读写该地址时引发的缺页异常(page fault),内核此时才通过伙伴系统(buddy system)从对应 zone 中找一个空闲页框,建立页表映射。
关键点在于:用户态看到的是虚拟地址,而内核按需把物理页“贴”上去。如果此时空闲页不足,就会立刻进入回收流程——不是等 malloc 返回失败,而是卡在 page fault 阶段。
- 小对象(slab 或
slub分配器,复用已缓存的对象,避免频繁拆分/合并页 - 大块内存(如 mmap 分配)可能直接从伙伴系统拿整页或多页,不经过 slab
- 匿名页(堆、栈)没有磁盘后备,回收时必须写入 swap;文件页(page cache)若干净(未修改),可直接丢弃;若脏,则需先回写磁盘
内存回收触发:三个水位线(WMARK_MIN/WMARK_LOW/WMARK_HIGH)怎么管事
内核为每个内存 zone 维护三条水位线,它们不是固定值,而是随系统总内存动态计算:WMARK_MIN 是底线,低于它就触发同步的直接回收(direct reclaim),当前进程会被卡住;WMARK_LOW 是警戒线,唤醒 kswapd 后台线程开始异步回收;WMARK_HIGH 是目标线,kswapd 回收到这里就停手。
常见误解是“swap 开了就没事”,其实只要空闲页跌破 WMARK_MIN,哪怕 swap 还有空间,也会先卡住进程做 direct reclaim——因为换入换出太慢,内核宁可阻塞也要抢出几页。
- 可通过
cat /proc/zoneinfo | grep -A 10 "Node 0, zone.*Normal"查看各 zone 实时水位 - 调整
/proc/sys/vm/min_free_kbytes可间接改变所有水位线(增大它会抬高WMARK_MIN,让回收更早启动,但会减少可用内存) - 不要盲目调高
min_free_kbytes:在 64GB 内存机器上设成 2GB,等于凭空吃掉 3% 的内存,对容器密集场景尤其不友好
回收干了啥:LRU 链表 + 页面分类 + 不同策略
回收不是随机扫内存,而是按页面类型和活跃度分层处理:每个 zone 有两组 LRU 链表(Active/Inactive),再按页面性质拆成四类:anon_inactive(不活跃匿名页)、anon_active、file_inactive、file_active。回收优先从 anon_inactive 和 file_inactive 里取页。
区别对待是因为:文件页可丢弃或回写,成本低;匿名页只能 swap 或压缩(如果启用了 zswap)。而 LRU 并非纯时间排序——内核会根据页面被访问频率(通过 pgrefill、pgdeactivate 等计数器)动态升降页面在链表中的位置。
- 脏文件页(
file_dirty)必须先回写到磁盘才能回收,这会拖慢整个回收周期,也是 IO 尖峰的常见源头 -
zswap(内存压缩)默认不启用,需手动加载模块并配置zswap.enabled=1;它把anon_inactive压缩后存在内存中,避免写 swap,但会增加 CPU 开销 - 回收过程会跳过被
mlock()锁住的页、内核线程页、以及某些驱动独占的 DMA 页——这些是回收黑名单
OOM 杀手不是第一道防线,而是最后兜底
当 direct reclaim 跑完一轮仍凑不够页,内核就判定为 Out-of-Memory,并启动 oom_killer。它不看 CPU 占用,只算每个进程实际占用的物理内存页数(RSS),再叠加 oom_score_adj 调整分——数值越高的进程越容易被杀。
注意:OOM 触发前,系统往往已出现明显症状:top 里 %wa(IO wait)飙升、dmesg 打印 “Out of memory: Kill process ...”、/proc/meminfo 中 Inactive(anon) 持续接近零。这时再查进程 RSS 已经晚了,得倒推是谁长期霸占 anon_active 链表不释放。
- 用
ps aux --sort=-%mem | head -10快速定位内存大户,但要注意 RSS 包含共享库,未必是真实“罪魁” - 真正有效的是
cat /proc/[pid]/smaps | awk '/^Pss:/ {sum+=$2} END {print sum}',它统计 PSS(Proportional Set Size),按共享比例折算后更准 - 容器环境要特别注意:cgroup v1 的 memory limit 不触发 OOM Killer,而是直接
throttle进程,表现为卡死无响应,而非被 kill
水位线、LRU 链表、回收路径、OOM 触发条件——这几层环环相扣,改错一个参数(比如只调 swappiness)解决不了根本问题;真正要调的,往往是应用自身的内存使用模式,比如避免长生命周期缓存无淘汰策略。










