分配失败分层排查
应用报 ENOMEM 或 EAGAIN 并不代表物理内存真的耗尽,要按层核对:进程地址空间限制(ulimit -v 与 /proc/<pid>/limits)→ cgroup 限额(memory.max、pids.max)→ overcommit 模式与 CommitLimit → 映射数量与碎片(vm.max_map_count、大页)。每一层都会以「分配失败」的形式暴露,从最外层的进程限制逐层向内核对,比直接怀疑应用代码更快定位。
基本信息
| 进程限额层 | ulimit -v、/proc/<pid>/limits 的地址空间项 |
|---|---|
| cgroup 限额层 | memory.max、pids.max |
| overcommit 层 | vm.overcommit_memory 与 CommitLimit |
| 映射数量层 | vm.max_map_count |
| 碎片与大页层 | buddyinfo、大页配额 |
典型使用场景
- Java、内存数据库等大内存应用启动失败
- 容器内报内存不足但宿主看起来空闲
- 日志报 ENOMEM 而 free 显示可用内存充足
- mmap 失败且应用日志没有明确线索
- 排查地址空间限制与映射数量限制的影响
分层排查命令
分层排查命令
ulimit -v; grep -iE 'address space' /proc/<pid>/limits
grep -E 'CommitLimit|Committed_AS|MemAvailable' /proc/meminfo
sysctl vm.overcommit_memory vm.max_map_count
cat /sys/fs/cgroup/<组>/memory.max 2>/dev/null
wc -l < /proc/<pid>/maps # 映射段数量,与 max_map_count 对比
pmap -x <pid> | tail -3 # 映射总量概览
cat /proc/buddyinfo # 高阶空闲块,判断碎片情况各层与典型现象
| 层次 | 检查项 | 典型现象 |
|---|---|---|
| 进程限额 | ulimit -v 与 limits 地址空间 | ENOMEM 但系统内存充足 |
| cgroup 限额 | memory.max、pids.max | 容器内 OOM 或创建进程失败 |
| overcommit | overcommit_memory=2 与 CommitLimit | 大额分配被直接拒绝 |
| 映射数量 | vm.max_map_count | mmap 失败,常见于大量映射的程序 |
| 碎片与大页 | buddyinfo 与大页配额 | 高阶连续分配失败 |
报错点在应用层,根因却常在限额层;先纵向核对各层阈值,再去改应用代码,能省下大量无效排查。
部分应用(如某些内存数据库)官方文档建议放宽 overcommit 策略,这属于全局变更,变更前应阅读其部署要求并评估对整机的影响。
快速判断技巧
- MemFree 与 MemAvailable 都充足却报错,问题多半在限额层或映射数量层
- 容器内先读 cgroup 的 memory.max 与 memory.events,再去看宿主内存
- 报 ENOMEM 且进程映射段数量很大时,优先核对 vm.max_map_count
容易被忽略的两项
- 线程栈与映射总量:线程数量多会同时消耗地址空间与映射配额,两个上层限制都容易被撞到
- 大页配额:应用要求使用大页但预留不足时,报错现象也是内存不足,需要单独核对大页相关配置