Linux OOM Killer 的触发机制与 oom_score_adj 调优最佳实践

3次阅读

oom killer 在系统物理内存彻底耗尽、无法回收足够页帧时被触发,内核遍历用户态进程,按 badness 分数(rss+swap 占用 × oom_score_adj 归一化系数 ÷ 总可用页数)选择最高者发送 sigkill。

Linux OOM Killer 的触发机制与 oom_score_adj 调优最佳实践

linux OOM Killer 在系统内存严重不足时被内核触发,目的是终止一个或多个进程以释放内存、防止系统完全僵死。它不是随机选进程杀,而是基于每个进程的 oom_score_adj 值和实际内存占用综合计算“OOM 优先级”,值越高越容易被选中。理解其触发逻辑并合理调优 oom_score_adj,是保障关键服务稳定性的重要运维手段。

OOM Killer 是如何被触发的?

当内核尝试分配内存(如通过 alloc_pages)但无法从 buddy system 或 slab 中获取足够页帧,且所有可回收内存(page cache、slab、swap 等)已基本耗尽时,会进入 OOM 流程。此时内核遍历所有用户态进程(忽略内核线程),为每个进程计算一个 badness 分数:

  • 基础分 = 进程 RSS + Swap 使用量(单位:页)
  • 再乘以 oom_score_adj 归一化系数(范围 -1000 ~ +1000;-1000 表示永不 kill,0 是默认值,+1000 表示最优先 kill)
  • 最后除以总可用内存页数做归一化,得到最终 badness

分数最高的进程被选中并发SIGKILL。注意:OOM 并不依赖 swap 是否启用,即使禁用 swap,只要物理内存彻底耗尽,同样会触发。

oom_score_adj 的作用与合法取值

/proc/[pid]/oom_score_adj 是用户可控的接口,用于显式影响 OOM 评分权重。它不是直接设置“分数”,而是调节内核对进程内存“危害性”的判定倾向:

  • -1000:进程被标记为 OOM-immune,内核跳过该进程(需 CAP_SYS_RESOURCE 权限)
  • 0:默认值,按实际内存使用参与评分
  • +500 ~ +1000:适合短命、高内存波动但非关键的进程(如编译任务、临时脚本)
  • -500 ~ -999:适合长期运行、低内存但不可中断的关键服务(如数据库主进程、容器 runtime)

注意:oom_score_adj 不改变进程真实内存占用,也不阻止其他机制(如 cgroup memory limit)的限制行为。

生产环境调优建议

盲目设 -1000 存在风险——若关键进程本身内存泄漏,可能拖垮整个系统。推荐分层策略:

  • 容器场景:在 docker run 或 Pod spec 中使用 --oom-score-adj(Docker)或 securityContext.oomScoreAdjkubernetes),避免手动改 /proc
  • systemd 服务:在 service unit 文件中添加 OOMScoreAdjust=-500,比启动后 echo 更可靠
  • 监控先行:定期采集 /proc/*/oom_score/proc/*/status 中的 MMURSS 字段,结合 dmesg -T | grep -i "killed process" 定位被杀原因
  • 避免全局设死值:同一类服务在不同负载下内存表现不同,应结合 cgroup v2 memory.max + memory.low 做更精细的资源隔离

常见误区与验证方法

很多人误以为调低 oom_score_adj 就能“保命”,但忽略了根本矛盾在于内存是否真的够用:

  • 误区:给 mysql 设 -1000 后仍被 kill → 实际可能是其子进程(如备份脚本、UDF)未设限,或内核因 page cache 耗尽而误判
  • 验证命令cat /proc/$(pgrep mysqld)/oom_score_adj 查当前值;grep -i "out of memory" /var/log/kern.log 看完整 OOM 日志
  • 调试技巧:临时用 echo -500 > /proc/$(pidof nginx)/oom_score_adj 测试效果,观察后续 OOM 是否转向其他进程

真正健壮的方案,是把 oom_score_adj 当作最后一道保险,而非替代内存容量规划、泄漏排查和 cgroup 配额管理。

text=ZqhQzanResources