OOM Killer 先杀谁?读懂 Linux 内存不足时的自保逻辑
凌晨三点被告警吵醒,登录服务器一看,核心服务进程不见了,日志里只有一句:
Out of memory: Killed process 12345 (java) ...
这不是服务自己崩溃,而是 Linux 内核在内存耗尽时动了手:OOM Killer(内存溢出终结者)挑了一个进程杀掉,用来保住整个系统。本文把它的选择逻辑、复盘方法和预防手段一次讲清。
一、内存耗尽前,内核已经自救过两次
OOM Killer 不是第一道防线。内存紧张时,内核会先做两件事:回收 page cache,把文件缓存腾出来;把不活跃的匿名内存换出到 swap。只有这两招都顶不住、available 内存见底时,才会轮到 OOM Killer。
所以看内存不能只看 free 那一行的 used。free -m 里真正要紧的是 available:它表示还能分给新进程的内存。cache 占得再高也不必慌,那是可回收的;available 持续走低、swap 也被用满,才是真正的危险信号。很多“内存满了”的误报,都是把 cache 当成了泄漏。
二、OOM Killer 按什么标准挑“牺牲者”
每个进程在内核里都有一个分数,可以直接查看:
cat /proc/<pid>/oom_score
分数越高,越先被杀。打分大致遵循三条规则:占用的内存越多,分越高;进程的子进程越多,牵连的分也越高;运行时间越长、越重要的系统进程,分会相对低一些。root 用户运行的进程通常也会得到一点优待,但这只是倾向,不是免死金牌。
真正的“免死金牌”是 oom_score_adj,取值范围是 -1000 到 1000:
echo -500 > /proc/<pid>/oom_score_adj
-1000 表示永不被杀,数值越大越容易被选上。生产上常见的做法,是给 sshd、监控 agent 这类“救命”进程调低分数,给可以重启的无状态 worker 调高分数。但要注意,-1000 不要随便用:被保护的进程如果真的泄漏,内核杀不掉它,就会去杀别的进程,最终可能连系统都保不住。
三、事后复盘:三条命令找到“凶手”
服务被杀后,先别急着重启,用这三条命令把现场留存下来:
dmesg -T | grep -i "out of memory"
journalctl -k -b | grep -i oom
grep -E "MemTotal|MemAvailable|SwapTotal|SwapFree" /proc/meminfo
dmesg 会记录被杀进程的名字、PID、当时占用的内存,以及触发时的内存水位。结合应用自己的日志,就能判断是某个接口突发大流量撑爆了内存,还是慢泄漏积累了几天终于越线。前者要限流、要降级,后者要修代码,两者的解法完全不同。
四、预防:让它“死得明白,活得更久”
最有效的预防,是在 OOM Killer 动手之前给进程画好边界。用 systemd 管理服务时,可以直接限制内存:
[Service]
MemoryMax=2G
MemoryHigh=1.5G
超过 MemoryHigh 会先被节流,超过 MemoryMax 进程会被 systemd 结束,现场干净、原因明确,比等内核随机挑一个进程杀掉要可控得多。Java、Go 这类运行时也要记得显式限制堆大小,不要让堆跟着机器内存一起“自由生长”。
另外两件事同样重要:给 available 内存设告警阈值,比如低于总内存的 10% 就告警,留出处理时间;上线前做一次内存压测,确认峰值流量下内存曲线的形状,而不是等半夜被告警叫醒。
OOM Killer 不是敌人,它是内核在极端情况下的最后一道保险。理解它的打分规则、留好复盘证据、提前设好上限,下一次内存告警来的时候,你就不用再靠猜来定位问题了。
- 点赞
- 收藏
- 关注作者
评论(0)