Linux OOM Killer

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 值 基本不影响了 已移除

最关键的两点:

  1. 允许内存上限(allowed memory) 取决于上下文:
    • 全局 OOM → 整个系统的可分配内存
    • cpuset / mempolicy 限制 → 该 cpuset 内的内存
    • memcg(容器)OOM → 该 cgroup 的 memory.max / memory.high
    • 如果有 memcg 限制,则优先在 cgroup 内部杀进程
  2. 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 杀掉?

  1. dmesg | grep -i "out of memory"
  2. journalctl -k | grep -i "killed process"
  3. /var/log/messages 或 syslog 中搜索 "Out of memory: Killed process"
  4. 进程退出码 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

  1. 设置合理的内存限制
    • 容器 → memory.limit_in_bytes / memory.high
    • systemd → MemoryMax=
    • 物理机 → 尽量别把所有内存都给业务
  2. 关键进程设 -1000
    • sshd、systemd、监控 agent、配置中心等必须 -1000
  3. 可牺牲进程设正值
    • 批处理任务、编译任务、压测进程设 +300 ~ +800
  4. 开启 earlyoom 或 systemd-oomd
    • earlyoom:用户态,提前在内存压力大时杀进程(推荐)
    • systemd-oomd:现代系统自带(需 cgroup v2)
  5. 监控 swap 使用率 + OOM 事件
    • Prometheus + node_exporter + alert rule
相关推荐
草履虫建模2 小时前
力扣算法 1768. 交替合并字符串
java·开发语言·算法·leetcode·职场和发展·idea·基础
naruto_lnq4 小时前
分布式系统安全通信
开发语言·c++·算法
天才奇男子4 小时前
HAProxy高级功能全解析
linux·运维·服务器·微服务·云原生
学嵌入式的小杨同学4 小时前
【Linux 封神之路】信号编程全解析:从信号基础到 MP3 播放器实战(含核心 API 与避坑指南)
java·linux·c语言·开发语言·vscode·vim·ux
Re.不晚5 小时前
Java入门17——异常
java·开发语言
酥暮沐5 小时前
iscsi部署网络存储
linux·网络·存储·iscsi
精彩极了吧5 小时前
C语言基本语法-自定义类型:结构体&联合体&枚举
c语言·开发语言·枚举·结构体·内存对齐·位段·联合
❀͜͡傀儡师5 小时前
centos 7部署dns服务器
linux·服务器·centos·dns
Dying.Light5 小时前
Linux部署问题
linux·运维·服务器
S19015 小时前
Linux的常用指令
linux·运维·服务器