Linux 的 overcommit 是内核的一场豪赌
调整 vm.overcommit_memory,把从内核盲目乐观里让出的那部分判断交回你自己的预算,最终决定谁在 OOM 时被牺牲。 为什么 malloc 很少失败 vm.overcommit_memory 默认为 0,处在启发式模式:它几乎对每一个 mmap 和 malloc 都说好,只要系统还大概承担得起;只有当内存真正吃紧时才拒绝。因为虚拟内存的承诺本身几乎零成本,可容纳的页又是点到即给,所以绝大多数分配都会成功,普通进程以近乎免费的方式活得好好的。这份慷慨的代价是内核可以超卖远多于物理内存。它赌你不会真正写满每一页;一旦太多字节真的落到内存里,总有人要用一次 OOM kill 来买单。 那个开关 vm.overcommit_memory 让你选择由谁来拿预算。默认 0 是启发式超卖:内核只拒绝那些明显不可能兑现的请求,其余几乎照单全收,赌的是多数进程永远不会触碰自己申请的每一个字节。设为 1 表示总是允许超卖:内核几乎不再设防,适合那些映射巨大但大多保持稀疏、长期不触碰的程序,代价是所有失败都被推迟到回收路径或杀手裁决那一刻。设为 2 则是严格记账:凡是超出 CommitLimit 的申请都会在调用当场以内存不足失败,这个上限大致等于交换空间加上物理内存乘以 overcommit_ratio 的比例,当 overcommit_kbytes 非零时以后者为准。两种极端都不免费:总是超卖会把压力藏到整机停顿才爆发,严格记账则会让那些申请慷慨但实际触碰很少的程序直接启动失败,应当按你想控制的风险来选。 启发式对严格上限 当账最终要平的那天到来,真正的问题是谁来买单。启发式模式下,内核会继续超卖,并在内存压力下随机挑一个牺牲品;严格模式下,边界事先画好,失败在 malloc 这一步就浮出水面,由运维而不是调度器决定哪个进程去吞这枚 OOM kill。容器与虚拟机通常会把它调成受控值:一台代表许多 guest 一起超卖的主机,在需求同时冲高时可能把自己的底盘一起压垮。笔记本可以大方一点;而一台绝对不能悄悄丢掉某个工作进程的产线上,就应当把选择说得明明白白。 坏掉的样子 在默认启发式模式下,最让人困惑的故障是:一次巨大的 malloc 或 mmap 明明返回成功,进程却在稍后被杀掉。申请瞬间还没有触碰任何页面,看起来一切正常;等进程真正写入这些页面、内存开始吃紧,内核杀手就会挑选一个牺牲品,而它往往不是当初超额申请的那个,而是旁边体积最大的进程。日志里只剩一句内存不足杀进程,却找不到任何一次分配失败,排查很容易追错二进制文件,真正的超卖源头反而藏在身后。 ...