OOM Killer 选择了哪个进程:分数、限制与证据收集
我在排查 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 内触发,组外进程不会成为这次事件的候选者。

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 增量。

这样复盘时就能依次回答:事件发生在哪个限制域;当时哪些任务有资格被选;策略偏置是否存在;被杀进程为什么在候选集合中更突出;最终应该修正限制、应用增长还是系统容量。证据链完整后,“为什么偏偏杀了它”就不再靠猜。
参考资料
- 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 选择,但不会增加可用内存。若没有配套容量和限制设计,压力可能转嫁给代理、日志、监控或同组其他进程,整体可用性反而下降。
在 Go 与 C 之间传递字节缓冲区而不保留悬空指针
- 上一篇
- 在 Go 与 C 之间传递字节缓冲区而不保留悬空指针
- 下一篇
- cgo 调用频繁时性能下降来自哪里,怎样批量化边界调用
-
- 文章 · linux | 3小时前 |
- ext4 与 XFS 在线扩容前后分别要核对什么
- 179浏览 收藏
-
- 文章 · linux | 5小时前 | 容器 · Linux · 容器隔离 mount namespace PID namespace linux namespace network namespace
- 用 namespace 理解容器进程、网络与挂载隔离
- 268浏览 收藏
-
- 文章 · linux | 8小时前 | Linux · 文件描述符 ·
- 日志轮转后服务仍写旧文件,文件描述符发生了什么
- 490浏览 收藏
-
- 文章 · linux | 10小时前 | 运维 · SSH Linux安全 sshd_config 服务加固 密钥登录
- SSH 只允许密钥登录后还要收紧哪些服务边界
- 297浏览 收藏
-
- 文章 · linux | 1天前 | Linux ·
- Linux 负载高但 CPU 空闲,怎样区分 I/O 等待与锁等待
- 398浏览 收藏
-
- 文章 · linux | 1天前 | 磁盘配额 Linux磁盘空间 No space left on device inode耗尽 ext4保留块
- 磁盘空间没满却无法写入:inode、配额与保留块检查
- 326浏览 收藏
-
- 文章 · linux | 1天前 | Linux ·
- nftables 为容器主机建立最小入站规则
- 373浏览 收藏
-
- 文章 · linux | 1天前 | linux运维 · 故障排查 · 服务管理 · systemctl journalctl RestartSec StartLimitBurst Restart systemd 服务
- systemd 服务反复重启:从退出码到速率限制排查
- 432浏览 收藏
-
- 文章 · linux | 1天前 | Linux ·
- Linux cgroup v2 限制服务 CPU 与内存的完整思路
- 150浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 379次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 450次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 460次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 403次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 231次使用
-
- Java GC 日志出现 humongous allocation 时怎么判断
- 2026-09-15 184浏览
-
- Linux cgroup v2 memory.events 怎么看:区分回收、限额与 OOM 的证据链
- 2026-08-16 456浏览
-
- Linux 服务为什么会被 OOM 杀掉:MemoryMax、日志证据与恢复边界
- 2026-08-26 462浏览
-
- Linux pidstat 怎么区分 CPU 忙和 I/O 等待:进程时间、上下文切换与复测方法
- 2026-08-26 252浏览
-
- Linux 新连接偶发超时但端口没耗尽:conntrack 表、丢包计数与回收参数排查
- 2026-08-27 301浏览

