进程莫名被杀?OOM killer 排查指南
网站突然 502,进程莫名其妙没了,八成是 OOM killer 动的手。小够教你从 dmesg 确认、找元凶、临时缓解到根治,内存不足的完整排查链路。
我是小够。最诡异的故障之一:网站好好的,突然 502,登录一看,应用进程没了,没有报错,没有日志,就像凭空消失。这八成是 OOM killer 干的——系统内存耗尽,内核亲自下场杀进程保命。
确认是不是 OOM killer
dmesg | grep -i "oom\|killed process"
看到类似这样的输出,就是它:
Out of memory: Killed process 1234 (python) total-vm:..., anon-rss:...
也可以查系统日志:journalctl -k | grep -i oom。确认了再往下查,别一上来就加内存,可能是别的问题。
谁被杀了,为什么是它
OOM killer 不是随机杀人,它按一套评分来:占用内存多、运行时间短、非系统关键进程的更容易被选中。你的应用进程通常就是完美目标——吃内存多,又不是 sshd 这种系统进程。
看 dmesg 那行里的 anon-rss,就是被杀进程当时占用的内存,心里有个数。
临时缓解:先让服务活着
- 加 swap:2G swapfile 五分钟配好,给系统一个缓冲垫(前面讲过)
- 降应用的内存占用:比如 PHP-FPM 子进程数调小、MySQL 的
innodb_buffer_pool_size调小 - 重启大户:有些应用有内存泄漏,定时重启是土办法但有效
这些是止血,不是治病。
根治:找到吃内存的元凶
# 看当前谁吃内存最多
ps aux --sort=-%mem | head -10
# 看内存都去哪了
free -h
常见元凶排行:
- 数据库缓存配太大:
innodb_buffer_pool_size设成了内存的 80%,再跑个应用直接爆 - PHP-FPM/Worker 开太多:每个子进程几十 MB,几十个加起来很可观
- 内存泄漏:内存缓慢上涨,重启后从低点重新涨,基本就是泄漏,修代码或定期重启
- 被入侵挖矿:CPU 100% + 内存异常,先查这个,
top按内存排序看到陌生进程先警惕
oom_score_adj:保护关键进程
实在没办法,可以调低关键进程被杀的优先级(分数越低越不容易被杀,-1000 表示永不杀):
# 让 sshd 尽量不被杀(系统默认已经保护了,一般不用动)
echo -500 | sudo tee /proc/<pid>/oom_score_adj
小够提醒:别给应用进程设 -1000。OOM killer 杀应用是保系统,应用不死,系统死了更惨。调分数是精细活,新手别碰。
预防:监控比排查重要
- 内存用到 80% 就告警,别等到 100%
- 画内存使用曲线,缓慢上涨的要警惕(泄漏信号)
- 小内存 VPS(1G/2G)标配 swap + 内存告警
小够的结论
OOM killer 是症状,不是病因。dmesg 确认 → 找元凶 → 止血 → 根治 → 上监控,这条链路走一遍,下次内存再报警,你就知道该看哪了。记住:频繁 OOM 的唯一正解是加内存或优化应用,swap 和调参都是缓冲。
cgroup 内存限制:给应用画个圈
与其让 OOM killer 全系统随机杀,不如用 cgroup 给应用画个内存上限,超了只杀圈里的:
# systemd 服务里加两行
[Service]
MemoryMax=1G
MemoryHigh=800M
MemoryHigh 是软限制(超了开始节流),MemoryMax 是硬限制(超了就杀这个服务的进程)。这样 OOM 的影响被关在笼子里,不会波及 sshd、数据库这些关键服务。
Docker 用户更熟:docker run -m 1g 就是干这个的。
earlyoom:用户态的提前预警
内核的 OOM killer 是最后手段,earlyoom 是用户态的"提前量":内存还剩 10% 时就开始按规则杀,比内核更早、更可控:
sudo apt install earlyoom
sudo systemctl enable --now earlyoom
适合桌面和小 VPS,服务器上用了 cgroup 限制后一般不需要。
实战:MySQL + PHP 的经典爆内存
小够见过最多的组合:1G 内存 VPS,MySQL 默认配置 + PHP-FPM 默认配置,直接爆。
# MySQL:innodb_buffer_pool_size 别超过内存的 50%
innodb_buffer_pool_size = 256M # 1G 机器上
# PHP-FPM:算一下能开几个子进程
; 每个子进程约 30-50M,1G 机器开 10 个左右
pm.max_children = 10
公式:(总内存 - 系统预留 300M)/ 单进程内存 = 最大进程数。先算再配,别用默认。
案例复盘:缓慢上涨型泄漏
现象:机器每周三凌晨被 OOM,重启后正常,下周三又来。
排查:画出 7 天内存曲线,看到一条缓慢上升的直线。用 ps aux --sort=-%mem 每天记录,锁定一个 Python 脚本的内存每天涨 100M。
根因:脚本里有个全局列表只增不减。修了一行代码,世界清净了。
教训:OOM 排查一定要看趋势,free -h 只看现在,等于只看冰山一角。
常见问题
Q:加了 swap 还是 OOM?
A:swap 满了照样 OOM。swap 是缓冲,不是印钞机。free -h 看 swap 用了多少,长期占满就是内存真不够了。
Q:怎么知道是哪个进程被杀?
A:dmesg | grep -i "killed process",输出里有进程名和 PID。配个监控,dmesg 出现 oom 就告警。
Q:云商家的"突发性能实例"和 OOM 有关系吗? A:有。突发实例 CPU 是受限的,内存压力大时系统更慢,OOM 来得更突然。这种实例更要配好 swap 和内存告警。
用 cgroup 给全家桶画圈
不止单个服务,整组应用都可以圈起来。比如"网站全家桶"(nginx+php+mysql)一共只给 1.5G:
# 建一个 slice,里面跑网站相关服务
sudo systemctl set-property website.slice MemoryMax=1.5G
超了只杀圈里的,sshd、监控这些系统服务不受影响。这是 cgroup v2 的玩法,现代发行版都支持。
内存告警:80% 就叫
#!/bin/bash
used=$(free | awk '/Mem/{printf "%.0f", $3/$2*100}')
[ $used -gt 80 ] && echo "内存已用 ${used}%" | mail -s "内存告警" [email protected]
每天跑几次,或者接进监控。OOM 发生前一定有预兆,内存不会从 50% 瞬间到 100%(挖矿和内存泄漏除外,那个看的是趋势)。
常见问题
Q:dmesg 里没有 oom 记录,但进程还是没了?
A:那可能不是 OOM:systemd 的 MemoryMax 杀进程不走 OOM killer 的日志;或者是段错误(segfault)自己崩的。journalctl -u xxx 和 coredumpctl 查。
Q:Java 应用老被杀?
A:JVM 的堆内存 + 元空间 + 线程栈,加起来经常超预期。-Xmx 设小点,给系统留 30% 余量。Java 在容器/小内存机器上是 OOM 重灾区。
Q:swap 加了,系统变慢了? A:频繁换页(swap in/out 很高)说明内存真的不够,swap 只是在"死"和"慢死"之间选了后者。这时候就别调参了,加内存或减负载。