服务器入坑指北:内存超额承诺的取舍
物理内存明明还有,新进程却申请不到;或者反过来——承诺了一堆、平时没事,某天一起兑现就雪崩;也不知道该看哪个数判断"危不危险"。
在你算比值之前,先把这条前提看清:承诺不是已用,两者必须分开看——承诺总量是"允许被申请走的地址空间",已用才是真占的物理内存。承诺可以远大于物理内存而不立刻出事——出事发生在"一起兑现"的那一刻。
这篇给三段可直接粘贴的内容:第一段只读(一次读齐)、第二段只读(算两式)、第三段含写操作(改策略并读回,标回滚点;容器侧另算一遍)。取值用占位符,字段名、参数名与信号名照写。
承诺发生在建映射那一刻
进程申请内存的动作(mmap 建立映射)就是"承诺"发生的时点——此时还没真占页。
所以"申请成功"不代表"用得起来";而 MAP_NORESERVE 是"显式绕过承诺检查"的那面旗——它是超额承诺的危险用法,不是推荐做法:它表示不为该映射保留交换空间,也就是说你把本该由内核拦下的那次检查自己关掉了。
一次读齐块
cat /proc/meminfo # 一次拿到承诺与内存的全部读数
free -h # 物理内存与交换(人类可读)
vmstat -s # 汇总视图里再看一遍内存与交换
sysctl vm.overcommit_memory # 现读承诺策略当前值
判据:这一块里每个数都要认领自己是"承诺"还是"已用"——
- 承诺侧:
Committed_AS是已承诺地址空间总量,CommitLimit是当前可承诺上限; - 容量侧:
MemTotal是物理内存总量,SwapTotal是交换区总量; - 可用侧:
MemAvailable是可供新用途使用的估计值——它与"空闲量"不是一回事。
算两式
第一式:承诺比 ≈ 承诺总量 / (物理内存 + 交换),即 Committed_AS / (MemTotal + SwapTotal)。
算一遍(数值为演示值):Committed_AS 按 21474836480 字节(20 GiB)、MemTotal 按 17179869184(16 GiB)、SwapTotal 按 2147483648(2 GiB)⇒ 分母 17179869184 + 2147483648 = 19327352832;承诺比 = 21474836480 / 19327352832 ≈ 1.11。
比值大于 1 意味着什么:已经承诺出去的量超过了物理内存与交换的总和——它不必立刻出事(承诺不等于已用),但它正是"什么时候危险"的那个量。
第二式:严格档下的上限 ≈ 交换总量 + 物理内存 × 计入比例,即 SwapTotal + MemTotal × vm.overcommit_ratio。
接着算(同一组数往下走):计入比例按 0.5 ⇒ 2147483648 + 17179869184 * 0.5 = 2147483648 + 8589934592 = 10737418240(字节,10 GiB)。拿它与 CommitLimit 的现读值对照:对得上,说明当前上限就是按这一套算的;对不上,说明走的是另一档。
策略有三档:启发式(按经验允许一定超额)· 总是允许(最危险,只适合明确知道自己在做什么的场景)· 严格(按"交换 + 物理内存 × 计入比例"算上限)。另有以 KB 计 vm.overcommit_kbytes 的形态——用它替代比例时,上面的加法式换成分量直接相加。
危不危险看两个量
- 承诺比(上面第一式):系统性超额程度——它回答"这台机器整体承诺出去多少";
- 承诺与已用的差(
Committed_AS与MemAvailable的相对关系):差额越大,兑现时的冲击越大。
判据:只看"还剩多少物理内存"会漏掉这一整类风险——MemAvailable看着还有,但承诺已经排到很后面了,这就是"物理内存还有、新进程却申请不到"的成因。
策略与容器侧块
sysctl vm.overcommit_memory # 现读策略档:三档取值语义以现读为准
cat /sys/fs/cgroup/memory.max # 控制组硬上限(前提:须为统一层级 cgroup 环境,路径以现读为准)
cat /sys/fs/cgroup/memory.current # 控制组当前用量
dmesg --level=err # 只读错误级内核消息(前提:受限环境需特权或调 kernel.dmesg_restrict)
两本账不可互替:容器里的 memory.max / memory.current 是控制组侧的账,与宿主侧的 Committed_AS / CommitLimit 不是同一本——不得用宿主的读数去解释容器内的表现(反之亦然)。容器场景要在控制组那两个量上另算一遍。
dmesg --level=err 的前提要写在读之前:受限环境里它敲了没输出——成因是 kernel.dmesg_restrict 决定非特权用户能不能读内核日志(是它决定读权限,不是反过来);所以**“没输出"不等于"没发生”**。
承诺失衡的最终表现是不可捕获的强杀:SIGKILL 不可被捕获或忽略——多个进程一起被这样杀掉,通常就是承诺一起兑现的那一刻。
取舍:放开承诺,还是收紧承诺
- 放开:换更高的部署密度;代价是把风险推迟到兑现时刻,而且兑现时是"多个进程一起失败";
- 收紧:换可预测性;代价是内存利用率下降、可能提前拒绝本来能跑的任务。
两条代价都要写出来。判据:这取决于"你能不能接受多个进程同时失败"——能接受就用放开换密度,不能接受就用收紧换确定性。改策略属高风险写操作,且影响整机、影响面最大——先记原值、留回滚形态。
策略侧改与读回:
sysctl -w vm.overcommit_memory=<取值> # 改策略档(高风险写操作:影响整机,需回滚点)
sysctl -w vm.overcommit_ratio=<百分比> # 严格档下的计入比例(同上,先记原值)
cat /proc/meminfo | grep -E 'Committed_AS|CommitLimit|MemTotal|SwapTotal' # 读回:四个量各是多少
free -h # 读回:物理内存与交换
现象到动作的判定表
| 现象 | 结论 | 动作 |
|---|---|---|
| 物理内存还有但申请失败 | 风险已经在兑现 | 算承诺比与"承诺与已用的差",不要只看 MemAvailable |
| 承诺比 > 1 但一切正常 | 这是延后风险,不是故障 | 记下这个比值作为基线,定期复查 |
| 多个进程同时被杀 | 承诺一起兑现 | 看 SIGKILL 的现场,收紧承诺或降密度 |
| 容器内表现与宿主读数不一致 | 两本账 | 在 memory.max / memory.current 上另算一遍 |
dmesg 读不到 |
受限,不是没发生 | 核 kernel.dmesg_restrict,改用别只读入口 |
改完怎么验证:每项一条判据
- 两个式子都算得出(承诺比、严格档上限);
- 承诺比与"承诺与已用的差"能分别解释一个现象;
- 策略档位能读出来,并与上限式对照;
- 容器侧与宿主侧分开算过;
- 有原值记录与回滚形态。
这篇不解决什么
- 讲 cgroup v2 内存上限与 OOM 取证的那篇管限制层(上限设多少、被杀了怎么归因);本篇管"承诺"这一层——为什么"申请得到"和"用得起来"是两件事。判定入口:看的是"被杀之后"还是"还没申请到"。
- 讲内存泄漏的观察与处置顺序的那篇管占用趋势;本篇管"承诺与已用的比"——一个看时间维度、一个看分配维度。
- 讲内存口径打架时信谁的那篇是读数口径之间的取舍;本篇讲的是承诺与已用这两个口径之间的关系。
踩坑清单
- 只看"还剩多少物理内存":会漏掉"承诺排到很后面"这一整类风险。
- 把"申请成功"当成"用得起来":承诺发生在建映射那一刻,页还没占。
- 把
MAP_NORESERVE当推荐做法:它是绕过承诺检查、把本该拦下的动作放行。 - 把
MemAvailable当成"承诺还没用完":可用估计与承诺余量是两个量,一个涨不代表另一个有空间。 - 改策略不记原值:影响整机,回滚没有依据。
- 把
dmesg无输出当成没发生:受kernel.dmesg_restrict管。 - 以为"承诺比高就一定会崩":它是风险,不是故障。
- 把"交换很大"当成安全的理由:严格档正是拿交换与计入比例一起算上限的。
- 点赞
- 收藏
- 关注作者
评论(0)