当前位置:首页 > 文章列表 > 文章 > linux > OOM Killer 选择了哪个进程:分数、限制与证据收集

OOM Killer 选择了哪个进程:分数、限制与证据收集

来源:17golang原创 2026-10-08 20:19:11 0浏览 收藏

我在排查 Linux OOM 时最容易犯的错,是看到某个服务被杀,就立刻认定它占内存最多,甚至直接把锅扣到“内存泄漏”上。实际上,OOM Killer 先看的是这次事件允许选择哪些任务,再结合内存占用和 oom_score_adj 计算坏度。被杀进程只是受害者,不一定是最早制造压力的进程。

先说结论
  • 先确认全局 OOM 还是 memory cgroup OOM;两者的候选进程范围不同。
  • 内核日志是事件回执,当前 /proc/PID/oom_score 只是现状,不是历史记录。
  • memory.max 是硬上限,memory.high 主要触发回收与节流;不要混为一谈。

一、问题现场:为什么不是 RSS 最大的进程

内核文档把 OOM 坏度描述为相对“允许内存”的评分。这里的允许内存不总是整台机器:全局 OOM 可能面向系统内存,cpuset 或内存策略会继续缩小范围;memory cgroup 达到硬限制时,候选任务又被限制在该 cgroup 内。于是,机器上 RSS 最大的进程如果不在本次候选集合里,就不会成为这个 cgroup OOM 的受害者。

oom_score_adj 还会给策略加偏置。它接受 -1000 到 1000;数值越大越容易被选,-1000 会让任务免于 OOM 选择。/proc/PID/oom_score 展示的是包含这项调整后的当前分数,导出值可能达到 2000。它很有用,但只有在进程还活着时才读得到。

二、先分清全局 OOM 还是 cgroup OOM

我现在不会从进程榜单开始,而是先问:是谁触发了内存边界?如果是全局 OOM,要看整机可用内存、交换区和当时允许的节点;如果是 cgroup OOM,则先查服务或容器所在组的 memory.max。cgroup v2 文档明确说明,无法把用量压回 memory.max 以下时,OOM Killer 会在该 cgroup 内触发,组外进程不会成为这次事件的候选者。

全局 OOM、cgroup OOM、候选集合和 oom_score_adj 的静态关系图
图1:OOM 候选范围静态关系图。先确定系统域或 cgroup 限制域,再解释候选集合与策略偏置。

memory.high 与 memory.max 也要分开。前者是节流和主动回收边界,单独达到它不会调用 OOM Killer;后者才是不可长期突破的硬限制。看到 memory.high 事件增加,只能说明压力与回收发生过,不能直接证明它杀了进程。

三、从内核日志锁定受害者与上下文

事后第一份证据应是内核日志。systemd 环境可用 journalctl -k 只看内核消息,-b 限定当前启动;没有持久 journal 时,再退回 dmesg。我会同时搜索事件描述、约束信息和最终的 killed process 记录:

# 只看当前启动的内核消息,并筛选 OOM 相关记录
journalctl -k -b --no-pager \
  | grep -Ei 'out of memory|oom-kill|killed process'

# 没有 systemd journal 时读取内核环形缓冲区
dmesg -T | grep -Ei 'out of memory|oom-kill|killed process'

不要只截最后一行。完整 OOM 片段可能包含触发任务、约束范围、节点掩码、任务内存信息以及被杀 PID。若启用了任务转储,日志还可能列出 PID、UID、虚拟内存、RSS、页表、交换占用和 oom_score_adj。这些字段比“服务刚好重启了”更接近内核当时的判断。

日志轮转太快时,至少把内核日志持久化,并让服务监控记录退出信号。受害进程退出后,它的 /proc/PID 会消失,事后再查同名新进程的分数,查到的是重启后的另一份状态。

四、读取分数、内存概览和 cgroup 归属

如果问题还能复现,或同一服务的其他工作进程仍在运行,可以先做只读快照。下面的命令把当前分数、策略偏置、RSS、交换占用和 cgroup 归属放在一起:

pid=1234

# 当前分数已经包含 oom_score_adj;它不是历史分数
cat "/proc/$pid/oom_score"
cat "/proc/$pid/oom_score_adj"

# 同时记录内存概览与线程数
grep -E '^(Name|VmRSS|VmSwap|Threads):' "/proc/$pid/status"

# 记录进程所属 cgroup,后续据此定位限制文件
cat "/proc/$pid/cgroup"

读取多个进程时,我会按当前 oom_score 排序,但不会把它当成预测保证。分数会随内存、候选范围和调整值变化;内核真正选择时看到的状态,可能与采样时不同。

# 采集仍存活进程的当前快照,读取失败时跳过
for d in /proc/[0-9]*; do
  pid=${d##*/}
  name=$(awk '/^Name:/{print $2}' "$d/status" 2>/dev/null) || continue
  rss=$(awk '/^VmRSS:/{print $2}' "$d/status" 2>/dev/null)
  score=$(cat "$d/oom_score" 2>/dev/null) || continue
  adj=$(cat "$d/oom_score_adj" 2>/dev/null) || continue
  printf '%s\t%s\t%s\t%s\t%s\n' "$pid" "$name" "${rss:-0}" "$score" "$adj"
done | sort -k4,4nr | head -n 30

五、把 memory.max 与 memory.events 接上

cgroup v2 的 /proc/PID/cgroup 通常会出现类似 0::/system.slice/example.service 的统一层级路径。把它拼到 /sys/fs/cgroup,就能读取当前用量、上限和事件计数:

pid=1234

# 取得 cgroup v2 的相对路径
cg=$(awk -F: '$1=="0" {print $3}' "/proc/$pid/cgroup")
base="/sys/fs/cgroup$cg"

# current 是当前用量;max 是硬限制;high 是节流边界
cat "$base/memory.current"
cat "$base/memory.max"
cat "$base/memory.high"

# local 只统计当前 cgroup,避免把子组事件混进判断
cat "$base/memory.events.local"

memory.events.local 里值得关注的字段包括 high、max、oom、oom_kill 和 oom_group_kill。其中 max 说明用量即将越过硬限制并进入直接回收;oom 说明分配在限制内无法满足;oom_kill 记录被 OOM Killer 杀掉的进程数。它们是累计计数,所以要与监控中的增量时间点对齐,不能只凭一个非零值断言“刚刚发生”。

如果 memory.oom.group 为 1,内核会把这个 cgroup 当成不可分割的工作负载,尝试整组终止,避免留下半死不活的服务。不过 oom_score_adj=-1000 的受保护任务仍是例外。是否使用整组语义,应由服务完整性决定,而不是为了让日志更整齐。

六、修复方案:不要只调 oom_score_adj

我会把修复分成三类。第一类是限制不合理:服务正常峰值已经接近 memory.max,需要重新核算工作集、并发和缓存,逐步调整限制。第二类是增长失控:RSS 或堆使用量持续上升,需要结合应用堆快照、分配器指标和业务流量定位泄漏。第三类是系统余量不足:多个工作负载同时增长,应该降低并发、缩短队列、回收缓存或增加容量。

直接把关键进程设成 oom_score_adj=-1000 通常不是修复。它只让该任务不被选,内存压力仍然存在,可能转而杀死同组其他进程,甚至把系统拖到更糟的状态。真正需要偏置时,也应把原因、范围和回滚方式写进服务配置,并观察压力是否被转嫁。

完成调整后,我会验证三件事:在接近峰值的压测下 memory.current 是否留有余量;memory.events.local 的 high、max 与 oom 增量是否符合预期;应用延迟和错误率是否同时恢复。只看“没有再次被杀”还不足以证明问题解决。

七、建立事前证据收集

最关键的改进不是多背几个 OOM 参数,而是在事件前后都保留同一条证据链。我的最小采集集合是:持久化内核日志、周期性记录高分进程的 oom_score 与 oom_score_adj、保存 PID 到 cgroup 的映射、监控 memory.current、memory.max 和 memory.events.local 增量。

内核日志、进程快照和 cgroup 限制组成的 OOM 证据链关系图
图2:OOM 证据链静态关系图。内核日志确认事件,进程快照解释候选分数,cgroup 文件还原限制上下文。

这样复盘时就能依次回答:事件发生在哪个限制域;当时哪些任务有资格被选;策略偏置是否存在;被杀进程为什么在候选集合中更突出;最终应该修正限制、应用增长还是系统容量。证据链完整后,“为什么偏偏杀了它”就不再靠猜。

参考资料

  • Linux proc 文档:https://www.kernel.org/doc/html/latest/filesystems/proc.html
  • Linux cgroup v2 文档:https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html
  • journalctl 手册:https://www.freedesktop.org/software/systemd/man/latest/journalctl.html

相关问题

oom_score 最高的进程一定会被杀吗?

不能这样保证。先要落在本次 OOM 的候选集合内,分数还会随事件上下文和实时内存状态变化。当前快照只能解释趋势,内核日志才是实际事件回执。

memory.events 中 oom 增加,为什么 oom_kill 没增加?

达到 OOM 条件不等于一定成功执行了进程终止;某些分配可能失败并返回错误,计数含义也要结合对应 cgroup 和时间增量解释。

把数据库设成 oom_score_adj=-1000 是否更安全?

它会让数据库免于 OOM 选择,但不会增加可用内存。若没有配套容量和限制设计,压力可能转嫁给代理、日志、监控或同组其他进程,整体可用性反而下降。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
在 Go 与 C 之间传递字节缓冲区而不保留悬空指针在 Go 与 C 之间传递字节缓冲区而不保留悬空指针
上一篇
在 Go 与 C 之间传递字节缓冲区而不保留悬空指针
cgo 调用频繁时性能下降来自哪里,怎样批量化边界调用
下一篇
cgo 调用频繁时性能下降来自哪里,怎样批量化边界调用
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    379次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    450次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    460次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    403次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    231次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码