发布时间:2026-09-25 04:30 更新时间:2026-09-25 04:30 阅读量:0
不少站长都遇到过这种怪事:网站访问突然 502,登录服务器一看 PHP-FPM 或 MySQL 进程不见了,系统日志里却找不到明显的报错;或者 SSH 连着连着就断开,重新连上后一切正常。这类现象有一个共同嫌疑对象——Linux 内核的 OOM Killer(Out Of Memory Killer)。它不是在系统内存真的耗尽时才出现,而是在内核认为「内存已经无法满足分配请求」时,挑选一个进程直接杀掉,用来保住整台机器不崩。问题在于,它杀进程不会弹窗通知你,只会在内核日志里留下一段记录。看懂这段记录,是定位问题的第一步。
Linux 的内存分配遵循「尽量满足、暂不回收」的策略,空闲内存会被 page cache 占满,这本身是正常现象。真正触发 OOM 的常见原因有三类:一是进程真实占用超过物理内存加 swap 的总量;二是容器或 cgroup 设置了 memory.limit,进程触顶后在该 cgroup 内被限制;三是短时间内大量申请内存(比如一次导入几 GB 的数据),内核来不及回收。触发后内核会遍历进程,按 oom_score 打分,分数高的先被杀。打分主要看进程占用的物理内存(RSS)大小、是否长期运行、是否是特权进程等,占用越多越容易被选中;可以通过 /proc/<pid>/oom_score_adj 做人为干预,取值范围通常是 -1000 到 1000,值越低越不容易被杀,具体以官方文档为准。
需要区分的是,宿主机层面的 OOM 和容器内的 OOM 表现不同:宿主机 OOM 会打印内核日志;容器被 cgroup 限制导致的 OOM,日志往往出现在内核日志里同时带有 cgroup 信息,也可能由容器运行时记录。排查前先想清楚是哪一层,能少走很多弯路。
最直接的入口是内核环形缓冲区。用 dmesg -T 把时间戳转成人类可读格式,再过滤 OOM 关键字:
# 查看带可读时间的 OOM 相关内核日志
dmesg -T | grep -i -E "oom|killed process"
只看最近一次 OOM 前后的上下文,行号自行调整
dmesg -T | grep -n -i "out of memory"
典型输出里会出现 Out of memory: Killed process 12345 (php-fpm) 这样一行,括号里是进程名,前面的数字是 PID。再往上翻几行,通常能看到 invoked oom-killer 以及各进程的内存明细表,里面 rss 一列就是被杀进程实际占用的物理内存。注意 PID 是杀进程那一刻的值,进程重启后 PID 会变,所以不要拿它去 ps 里找,意义不大。
如果系统使用 systemd 且 journald 持久化保存日志,journalctl 更适合做时间范围检索,尤其是机器重启后 dmesg 内容可能被覆盖的情况:
# 抓取本次启动以来的 OOM 记录
journalctl -k --since today | grep -i -E "oom|killed process"
指定时间段,配合业务报障时间点定位
journalctl -k --since "2026-09-01 10:00" --until "2026-09-01 10:30" | grep -i oom
直接看某个服务是否因内存被杀后重启
journalctl -u php-fpm --since today | tail -50
这里有个容易被忽略的点:journalctl -k 读的是内核消息,和 dmesg 内容基本一致;如果 journald 没有开启持久化存储,重启后日志就没了,建议在 /etc/systemd/journald.conf 里把 Storage=persistent 打开,并配合容量限制,避免日志把 /var/log 写满。如果机器上还有 rsyslog 在收内核日志,也可以到 /var/log/messages 或 /var/log/kern.log 里搜 oom,具体文件名随发行版不同,以实际环境为准。
拿到进程名只是开始,真正要回答的是「为什么内存不够」。建议按下面的顺序查:先看被杀进程的历史内存趋势,再看同机其他进程有没有异常增长,最后确认是物理内存不足还是 cgroup 限额太低。常用命令如下:
# 看当前内存与 swap 使用情况
free -h
按 RSS 排序看谁占内存最多,取前 10 行
ps -eo pid,ppid,user,rss,pmem,comm --sort=-rss | head -n 10
查看某个进程的 OOM 评分与调整值
cat /proc/$(pgrep -n php-fpm)/oom_score
cat /proc/$(pgrep -n php-fpm)/oom_score_adj
如果进程跑在容器里,还要看 cgroup 限制。以 systemd 管理的服务为例,可以用 systemctl show 服务名 | grep -i memory 查看 MemoryMax、MemoryHigh 等参数;容器场景则在宿主机上查看对应的 cgroup 目录或使用运行时自带的 inspect 命令,路径与字段随版本变化,以官方文档为准。很多时候宿主机内存还剩不少,但某个容器的 limit 设得太低,一样会触发 OOM。
降低误杀有几条实用思路:一是给关键进程设置合理的 oom_score_adj,比如让 sshd 更不容易被杀,避免失联;二是给数据库、PHP-FPM 这类内存大户设好上限,PHP-FPM 的 pm.max_children 要按单进程平均内存乘以进程数来估算,别让总内存超过物理内存;三是保留适量 swap,完全禁用 swap 会让突发内存申请更容易触发 OOM;四是容器明确配置 memory limit 和 requests,让调度和限制都符合预期。需要说明的是,这些手段只能减少误杀,内存真的不够时,加内存或拆分服务才是根本解法,不要指望靠调参数绕过容量问题。
最后给一个排查习惯:把 OOM 关键字纳入日常日志巡检,比如用 journalctl 定期导出 OOM 记录做趋势观察,一旦发现某服务每周都被杀一次,就说明容量或配置已经到了该调整的时候。看懂 dmesg 和 journalctl 里那几行记录,比事后重启服务有价值得多。
| 📑 | 📅 |
|---|---|
| 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 |
| Docker 容器内 Nginx 与宿主 Nginx 端口冲突排查 | 2026-09-24 |
| 内核日志里的 OOM 线索:oom_score、cgroup 限制与判断内存不足原因 | 2026-09-25 |
| Nginx与上游服务时间不同步导致签名校验失败排查 | 2026-09-25 |
| 宿主机与容器时间不一致导致接口签名失败排查 | 2026-09-25 |
| MySQL主从复制中断后如何安全恢复 | 2026-09-25 |
| Nginx 上游节点健康检查实战:max_fails、fail_timeout 与 backup 配置 | 2026-09-26 |