Linux 的 OOM Killer (Out-Of-Memory Killer)是内核在物理内存 + swap 真正耗尽 时触发的最后一道防线,它会有选择性地杀死一些进程来释放内存,避免整个系统彻底卡死(完全无响应,需要硬重启)。
1. 为什么需要 OOM Killer?
Linux 内核默认采用**内存过量分配(memory overcommit)**策略:
- 进程申请内存时(malloc、mmap 等),内核通常先答应(返回成功指针)
- 真正写内存(触发缺页中断)时才分配物理页
- 这导致虚拟内存总量可以远超物理内存+swap
当绝大多数进程都开始真正使用内存,且总需求 > 物理RAM + swap 时,就触发 OOM。
没有 OOM Killer 的后果 → 系统假死(soft lockup)、大量进程进入 D 状态、watchdog 触发重启、甚至内核 panic。
2. OOM 触发的主要路径(简要)
用户进程缺页 → handle_mm_fault()
→ __alloc_pages()
→ __alloc_pages_slowpath()
→ __alloc_pages_may_oom()
→ out_of_memory() ← 这里进入 OOM 主逻辑
3. 现代内核(2.6.36 之后,尤其是 5.x+)的打分逻辑 ------ 非常简单了
核心函数:oom_badness()(mm/oom_kill.c)
现在的打分公式(极大简化后)基本可以概括为:
badness = (进程当前使用的内存 / 该进程被允许使用的内存上限) × 1000 + oom_score_adj
| 因素 | 说明 | 大致影响 |
|---|---|---|
| 内存占用比例 | anon + file-backed + swap 等,占"允许内存上限"的百分比 | 主导因素(0~1000) |
| oom_score_adj | 用户可手动设置的调整值(-1000 ~ +1000) | 直接加到分数上 |
| 是否 root | 极轻微优惠(现代内核基本忽略或极小) | 几乎无影响 |
| 运行时间 | 现代内核基本不考虑了(以前长寿进程会减分) | 已移除 |
| nice 值 | 基本不影响了 | 已移除 |
最关键的两点:
- 允许内存上限(allowed memory) 取决于上下文:
- 全局 OOM → 整个系统的可分配内存
- cpuset / mempolicy 限制 → 该 cpuset 内的内存
- memcg(容器)OOM → 该 cgroup 的 memory.max / memory.high
- 如果有 memcg 限制,则优先在 cgroup 内部杀进程
- oom_score_adj 是目前唯一可靠可控的调整手段
| oom_score_adj 值 | 实际效果 | 典型使用场景 |
|---|---|---|
| -1000 | 完全免疫(分数永远 ≤0) | 最重要的系统服务(sshd、systemd 等) |
| -500 | 大幅降低被杀概率(≈减半内存权重) | 重要业务进程 |
| 0 | 默认,无调整 | 普通进程 |
| +500 | 大幅提高被杀概率(≈双倍内存权重) | 可牺牲的批处理任务 |
| +1000 | 最高优先级被杀 | 内存测试、压测脚本 |
4. 怎么查看和设置?
# 查看某个进程的当前分数和调整值
cat /proc/<pid>/oom_score
cat /proc/<pid>/oom_score_adj
# 举例:把 nginx 设为最高保护
sudo sh -c "echo -1000 > /proc/$(pidof nginx)/oom_score_adj"
# 容器常用写法(在容器内执行)
echo -1000 > /proc/self/oom_score_adj
# systemd 服务永久设置(推荐)
# 在 .service 文件中加:
# OOMScoreAdjust=-1000
5. 常见的 sysctl 参数(/etc/sysctl.conf)
| 参数 | 推荐值(生产) | 说明 |
|---|---|---|
| vm.panic_on_oom | 0 | 0=正常触发OOM killer,1=部分情况panic,2=强制panic(基本不用) |
| vm.oom_kill_allocating_task | 0 或 1 | 1=优先杀正在疯狂申请内存的那个进程(很多场景更合理) |
| vm.overcommit_memory | 0 或 1 | 0=启发式overcommit,1=总是允许,2=严格检查(不推荐生产打开) |
| vm.overcommit_ratio | 50~90 | overcommit_memory=0/1 时,overcommit 比例 |
6. 怎么判断是否被 OOM 杀掉?
- dmesg | grep -i "out of memory"
- journalctl -k | grep -i "killed process"
- /var/log/messages 或 syslog 中搜索 "Out of memory: Killed process"
- 进程退出码 137(128+9)通常是 SIGKILL,很可能是 OOM
典型日志样子:
Out of memory: Killed process 12345 (java) total-vm:8388608kB, anon-rss:7654321kB, file-rss:0kB, shmem-rss:0kB pgtables:15432kB oom_score_adj:0
7. 如何防止OOM
- 设置合理的内存限制
- 容器 → memory.limit_in_bytes / memory.high
- systemd → MemoryMax=
- 物理机 → 尽量别把所有内存都给业务
- 关键进程设 -1000
- sshd、systemd、监控 agent、配置中心等必须 -1000
- 可牺牲进程设正值
- 批处理任务、编译任务、压测进程设 +300 ~ +800
- 开启 earlyoom 或 systemd-oomd
- earlyoom:用户态,提前在内存压力大时杀进程(推荐)
- systemd-oomd:现代系统自带(需 cgroup v2)
- 监控 swap 使用率 + OOM 事件
- Prometheus + node_exporter + alert rule