Linux 内存未耗尽却无法创建进程的一次内核级排查
作者:da吃一鲸8862026.11.07 01:08浏览量:0简介:当系统提示 fork 失败、无法创建新进程时,很多人会直接判断为内存耗尽。但真实环境中常会出现 free 显示仍有可用内存、Swap 也大量空闲,进程却依然无法启动的矛盾现象。本文通过一次生产故障复盘,分析 Linux 内存超售、进程信用额度与 fork 失败之间的深层关系,帮助读者理解 free 输出为何会“说谎”、内核如何判断内存是否足够,以及如何从 dmesg、cgroup 和系统配置中定位真正瓶颈。
Linux 内存未耗尽却无法创建进程:一次内核级“假死”排查
一、从一次诡异的故障说起
在部分 Linux 服务器上,你可能会遇到下面这类错误:
-bash: fork: Cannot allocate memory
按照直觉,第一反应通常是物理内存不足。但真正进入系统执行 free -m 后,却可能看到如下结果:
total used free shared buff/cache availableMem: 15867 15010 112 41 744 353Swap: 4095 0 4095
从数字上看,物理内存仍有约 353 MB 可用,Swap 还有 3.5 GB 以上空闲。表面上的“可用资源”接近 4 GB,但系统却连一个轻量级 Shell 都无法 fork 出来。
这种情况不是传统意义上的内存耗尽,也不是某个数据库或应用突然失控,而是 Linux 内核在内存管理层面做出了一种更隐蔽的限制。它并不是在说“内存已经用完”,而是在说:“当前进程已经无法获得创建所需的内存担保。”
本文将围绕一次真实故障,从内核日志、内存记账机制、进程创建流程和系统配额四个维度逐步还原问题根因。
二、为什么 free 显示的“可用内存”并不可信
很多运维人员习惯将 free -m 中的 available 列理解为“还有多少内存可以随意使用”。但这个理解并不完全准确。
available 的计算逻辑大致可以表示为:
available ≈ free + 可回收的 page cache + 可回收的 slab 对象
也就是说,available 是一个估算值,表示在回收缓存之后系统可能为普通内存请求释放出的空间。但它并不等同于“当前没有任何约束地分配给任意进程”的内存量。
在 Linux 中,是否允许进程获得内存,不只看 available,还要受到以下因素影响:
- 内核的内存超售策略。
- 进程所属 cgroup 的内存限制。
- 内核为进程记账所预留的“承诺额度”。
- 当前地址空间、页表、栈、文件描述符等资源占用。
- 系统参数对 overcommit 行为的限制。
因此,available 较高不代表可以无限 fork 新进程。它只是反映了一段时间内的内存回收潜力,而不直接等于进程可获取的内存上限。
三、fork 失败与内存超售的关系
3.1 fork 为什么需要“内存担保”
fork() 创建子进程时,内核需要为新进程复制父进程的地址空间描述、页表、内核栈、线程结构等元数据。虽然 Linux 使用写时复制技术,不会立刻复制所有物理页,但内核仍然需要为这些新结构预留内存。
如果系统配置了严格的内存超售策略,内核会先执行 __vm_enough_memory 检查,判断当前请求是否可以被“承诺”。一旦判断未通过,就会直接返回 ENOMEM,即使用户态看到物理内存还有空闲。
3.2 Linux 的 overcommit 策略
Linux 内核通过 vm.overcommit_memory 参数控制内存承诺方式,常见取值如下:
| 值 | 含义 |
|---|---|
| 0 | 启发式超售,内核根据可用内存和请求大小估算是否允许 |
| 1 | 总是允许内存超售,几乎不拒绝普通分配 |
| 2 | 严格限制,不允许明显超过物理内存与 Swap 总和 |
在严格策略下,内核会维护一个“承诺内存”总量,任何大额分配都会被严格校验。如果系统长期运行大量进程,即使每个进程实际占用不高,承诺总量也可能提前达到上限。
此时,fork() 并不是因为用完了物理内存,而是因为内核认为“如果继续承诺下去,未来可能无法兑现”。
四、真实故障的排查过程
4.1 初步现象
某台用于数据库测试的 Linux 服务器出现异常:从 root 用户切换到普通数据库用户时,直接提示:
-bash: fork: Cannot allocate memory
同一时间执行内存检查,却发现物理内存与 Swap 均未耗尽。
4.2 查看内核日志
对于此类矛盾现象,第一步应检查内核环形缓冲区。使用:
dmesg -T
可以看到类似如下记录:
[Wed Jun 12 10:24:31 2024] __vm_enough_memory: pid: 20799, comm: su, not enough memory for the allocation
该日志表明,内核在 __vm_enough_memory 阶段拒绝了进程的内存请求。这里的关键不是物理内存耗尽,而是当前请求没有通过内核的内存承诺检查。
4.3 检查进程数量与内存承诺
接下来需要查看系统整体进程状态:
ps -eLf | wc -lcat /proc/meminfo | grep -E 'CommitLimit|Committed_AS|MemAvailable'
其中:
Committed_AS:当前系统已经承诺出去的内存总量。CommitLimit:内核允许承诺的最大内存量。- 如果
Committed_AS接近或超过CommitLimit,就会导致新进程创建失败。
例如:
CommitLimit: 8177668 kBCommitted_AS: 8014592 kBMemAvailable: 361712 kB
此时 available 仍有约 353 MB,但承诺内存已经接近上限。此时任何新的 fork() 都可能因为无法获得额外承诺而失败。
4.4 查找内存承诺的主要消耗者
可以通过遍历 /proc 下的进程信息,观察哪些进程的地址空间较大或线程数量异常:
ps -eo pid,ppid,cmd,%mem,rss,vsz --sort=-vsz | head -20
在某些场景中,一个应用可能并没有占用大量常驻内存,但其虚拟地址空间非常大,或者内部启动了过多线程。这类进程会显著抬高系统的内存承诺总额。
4.5 检查 cgroup 限制
在现代 Linux 发行版中,还需要关注 cgroup 的限制。即使宿主机内存充足,如果当前会话所在的 cgroup 设置了较低的内存上限,也会出现 fork 失败。
可以通过以下方式查看:
cat /sys/fs/cgroup/memory/memory.limit_in_bytescat /sys/fs/cgroup/memory/memory.usage_in_bytes
如果 limit 设置过小,而 usage 已接近限制,就会出现与物理内存无关的 ENOMEM。
五、为什么 Swap 空闲也不能解决 fork 失败
很多人会疑惑:Swap 还有 3.5 GB,为什么不能用来创建进程?
这是因为 fork() 在请求内存承诺时,内核并不简单地将 Swap 视为可直接分配的空间。Swap 更多承担物理页换出后的落盘能力,而不是可以在任意时刻用于满足所有分配请求的“现金”。
此外,如果系统处于内存压力状态,Swap 的写入速度、页回收速度都会受到影响。对于某些不可回收的内核结构,例如内核栈、页表和任务结构,Swap 的作用非常有限。
因此,Swap 空闲只能说明系统尚未大规模换出页面,并不代表内核会允许新的进程承诺继续增长。
六、如何避免和修复类似问题
6.1 调整内存超售策略
如果业务负载明确且物理内存规划充足,可以考虑调整内核策略:
sysctl vm.overcommit_memory=1
但需要注意,该配置会让内核几乎不再限制普通内存请求,可能在极端情况下触发 OOM Killer。更稳妥的方式是保持启发式策略,同时降低承诺内存占用。
6.2 控制进程数量和线程数量
对于数据库、中间件或测试环境,应重点关注单机进程数与线程总数。大量线程会显著增加内核栈、页表和任务结构的承诺内存。
可以通过系统配置控制用户可创建的进程数:
ulimit -u
也可以检查应用是否在线程使用上存在泄漏或无限扩展。
6.3 优化应用的内存申请方式
某些语言运行时或应用框架在启动时会预留大量虚拟地址空间,即使实际使用很少,也会提高承诺内存总量。对于此类应用,应结合其内存管理参数调整堆、栈和线程池大小。
6.4 结合监控与日志建立预警
建议持续监控以下指标:
Committed_AS与CommitLimit的比值。- 系统总进程数与线程数。
- cgroup 内存使用率。
- 内核日志中
__vm_enough_memory的出现频率。
当承诺内存接近上限时提前告警,而不是等用户报告无法登录或无法切换用户后才被动响应。
七、总结
表面上看,系统还有可用内存和大量 Swap,却无法创建进程,这并非 Linux 的行为异常,而是内核在内存管理上执行了更严格的承诺机制。
free 提供的是一个估算视角,而不是最终分配结论。进程能否成功创建,还需通过 fork() 的内存担保、overcommit 策略、内存承诺总额以及 cgroup 限制等多层校验。
对于运维和开发人员而言,理解这一层逻辑比单纯记住“内存不够就看 free”更有价值。面对 fork 失败,不应只检查剩余内存,而应顺藤摸瓜,查看内核日志、承诺内存和进程数量,才能找到真正的瓶颈所在。

登录后可评论,请前往 登录 或 注册