发布时间:2026-09-25 04:30 更新时间:2026-09-25 04:30 阅读量:0
服务器突然卡死、SSH 连不上,重启后翻看内核日志,常见一行“Out of memory: Killed process”。很多站长第一反应是“内存不够,加内存条”,但实际原因可能只是某个进程吃掉了大部分内存,系统本身余量充足。这篇文章从内核日志与 oom_score 切入,结合 cgroup 限制,讲清怎么判断到底是哪种情况,以及下一步该做什么。
OOM Killer 被触发时,内核会把现场信息写入环形缓冲区。用 dmesg 或者 journalctl -k 都能看到。先别急着看被杀的进程名,重点看这几行:
dmesg -T | grep -i -E 'oom|out of memory' -A 20
或者
journalctl -k --since "1 hour ago" | grep -i oom
日志里通常会出现 “Node 0 Normal free:xxxkB min:xxxkB low:xxxkB high:xxxkB”。这里 free 表示当时可用内存,min 是内核保留的最低水位。如果 free 已经低于 min,说明系统整体内存确实紧张;如果 free 还远高于 min,那多半是某个进程在瞬间申请了一大块内存,触发了分配失败,属于“单进程吃太多”的典型场景。
再往下看 “oom_score_adj” 和 “total_vm” 这类字段,它们对应被选中进程的评分和虚拟内存大小。内核选择杀谁,靠的就是 oom_score:分数越高越容易被杀。分数由进程实际占用内存(RSS)、swap 用量以及 oom_score_adj 共同决定,具体算法以内核文档为准。
每个进程在 /proc/<pid>/ 下都有 oom_score 和 oom_score_adj 两个文件。前者是只读的当前得分,后者是用户可调的偏移量,范围 -1000 到 1000。把某个关键进程的 oom_score_adj 调低,它就更不容易被杀;调高则相反。例如保护一个数据库进程:
echo -500 > /proc/$(pgrep -f mysqld | head -1)/oom_score_adj
查看当前分值
cat /proc/$(pgrep -f mysqld | head -1)/oom_score
注意,调整 oom_score_adj 只是降低被杀概率,不能解决内存不足的根本问题。如果进程本身有内存泄漏,早晚还会触发 OOM。
cgroup 则从另一个维度限制内存。在 cgroup v2 下,每个控制组有 memory.max 和 memory.current。当组内进程内存触及 memory.max,内核会先尝试回收,回收不动就在该组内触发 OOM,而不是杀整个系统的进程。这正是容器场景常见的情况:容器内存超限被杀,宿主整体内存却很充裕。查看方式:
# cgroup v2 路径示例,具体以实际环境为准
cat /sys/fs/cgroup/system.slice/docker-<容器ID>.scope/memory.max
cat /sys/fs/cgroup/system.slice/docker-<容器ID>.scope/memory.current
cat /sys/fs/cgroup/system.slice/docker-<容器ID>.scope/memory.events
memory.events 里的 oom 和 oom_kill 计数很关键。如果 oom_kill 持续增长,说明这个 cgroup 反复触顶,需要检查容器内存 limit 是否过小,或者应用本身内存占用是否失控。
把日志和实时数据对照,可以按下面几步走:
第一步,确认 OOM 发生时的系统水位。看日志中 free 与 min 的对比。free 低于 min,偏向系统整体不足;free 明显高于 min,偏向单进程突增。注意 free 是当时快照,不一定代表长期状态。
第二步,看被 OOM 的进程是谁。用 dmesg 里 “Killed process” 后面的 PID 和进程名,结合 journalctl 或应用日志确认它当时在做什么。如果是一个平时占用很小的进程突然被杀,可能是它申请了大块内存,也可能是别人先占满了内存,内核挑了个“好欺负”的。
第三步,观察长期趋势。用 free -m、vmstat 1 或者 sar -r 看内存曲线。如果 available 长期很低、swap 持续换入换出,说明系统整体内存吃紧;如果 available 长期健康,只是偶发 OOM,则重点查那个进程的内存分配行为。
free -m
vmstat 1 5
查看 cgroup 内存事件
cat /sys/fs/cgroup/memory.events 2>/dev/null || true
第四步,区分容器与宿主。容器内 OOM 不代表宿主内存不足,先看 docker stats 或 cgroup memory.current 与 memory.max 的差距。如果容器 limit 是 512M,而应用实际需要 800M,那就是 limit 设置问题,而不是宿主内存不够。
最后给一个实用建议:给关键业务进程设置合理的 oom_score_adj,给容器设置符合实际的内存 limit,并开启内存事件监控。遇到 OOM 时,先按日志水位和 cgroup 事件判断方向,再决定是扩容、调 limit,还是排查应用内存泄漏。内存问题往往不是加一条内存就能一劳永逸,找准原因才能少走弯路。
| 📑 | 📅 |
|---|---|
| Linux 服务器 OOM Killer 日志怎么看:dmesg 与 journalctl 定位被杀进程 | 2026-09-25 |
| Nginx 反代后协议错乱:X-Forwarded-Proto 排查与修复 | 2026-09-24 |
| Docker 容器内 systemctl 不可用:init 缺失与多进程管理的取舍 | 2026-09-24 |
| MySQL 8 大事务致 undo 膨胀与主从延迟:定位与拆分提交 | 2026-09-24 |
| Nginx 443 端口 SNI 分流:WebSocket 与普通请求共用配置 | 2026-09-24 |
| Nginx与上游服务时间不同步导致签名校验失败排查 | 2026-09-25 |
| 宿主机与容器时间不一致导致接口签名失败排查 | 2026-09-25 |
| MySQL主从复制中断后如何安全恢复 | 2026-09-25 |
| Nginx 上游节点健康检查实战:max_fails、fail_timeout 与 backup 配置 | 2026-09-26 |
| MySQL死锁日志定位实战:读懂SHOW ENGINE INNODB STATUS | 2026-09-26 |