登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  linux

Linux cgroup v2用 memory.events 区分 oom 与 oom_kill的实现方法

来源:17golang原创

时间:2026-09-15 23:21:15 281浏览 收藏

排查容器或服务被 OOM 影响时,先不要把看到的 “oom” 直接等同于“进程已经被杀”。在 cgroup v2 中,memory.events 里的 oom 记录的是内存分配已经逼近失败的事件,而 oom_kill 记录的是 OOM killer 实际杀掉的进程数。两者要和 memory.maxmemory.current 一起看,结论才不会偏。

要点速览
  • oom 增加,表示一次分配走到了 OOM 处理边界,不保证已经发生进程终止。
  • oom_kill 增加,才说明有进程被某种 OOM killer 杀掉。
  • memory.events 默认包含子 cgroup 的层级统计;只看当前组要读 memory.events.local

先把 cgroup 的内存边界读清楚

我更习惯先把观察对象固定成一个 cgroup 目录,再读四个文件。下面的路径只是示例,实际环境要替换成服务所在的 cgroup:

# 只读取目标 cgroup 的内存边界和事件文件,不扫描宿主机所有进程
CG=/sys/fs/cgroup/demo-worker

# memory.max 是硬上限,memory.current 是当前统计用量
printf 'max: '; cat "$CG/memory.max"
printf 'current: '; cat "$CG/memory.current"
printf '\n[events]\n'; cat "$CG/memory.events"
printf '\n[local events]\n'; cat "$CG/memory.events.local" 2>/dev/null || true

memory.max 返回字节数,也可能是 max,表示该组没有设置数值上限;memory.current 用来判断当前用量是否贴近上限。memory.events 是按键名组织的只读文件,不能假设字段永远按某个固定行号出现。

Linux cgroup v2 中 memory.max、memory.current 与 memory.events 的内存边界结构说明图
图1:cgroup v2 内存边界说明图,展示上限、当前用量与事件字段的静态关系。

按字段读取 oom、oom_kill 和 max

生产脚本不要用 sed -n '4p' 这种按行号取值方式。用键名读取,既能容忍内核新增字段,也能把一次快照保存下来与下一次比较:

# 从 key value 文件中按字段名取计数;字段缺失时返回 0,便于兼容旧环境
event_value() {
  local key="$1"
  awk -v wanted="$key" '$1 == wanted { print $2; found=1; exit } END { if (!found) print 0 }' "$CG/memory.events"
}

# 这些是单调计数器,重点看本次采样相对上次的增量
OOM=$(event_value oom)
OOM_KILL=$(event_value oom_kill)
MAX_HIT=$(event_value max)
printf 'oom=%s oom_kill=%s max=%s\n' "$OOM" "$OOM_KILL" "$MAX_HIT"

内核文档对三个字段的含义不同:max 表示用量曾经要超过 memory.maxoom 表示已经到达限制且分配即将失败;oom_kill 表示属于该 cgroup 的进程被 OOM killer 终止。也就是说,oom 增加而 oom_kill 不变,并不矛盾,可能是分配以 -ENOMEM 返回、重试后成功,或者调用路径不适合触发 OOM killer。

oom 和 oom_kill 要按两个事实判断

可以把判断拆成“资源边界事实”和“进程结果事实”。第一组只说明内存分配遇到了压力,第二组才说明业务进程真的少了。不要用一条计数替代另一条:

观察结果应作的判断下一步
max 增加,oom 不变触碰过硬上限,但尚未确认 OOM 分配失败对照 memory.current 与业务峰值
oom 增加,oom_kill 不变出现过 OOM 边界,可能由分配失败或重试结束看服务错误处理和申请内存的调用方
oom_kill 增加确实发生了进程终止记录被杀进程、重启次数和内存上限变更

还有一个容易误读的点:memory.events 默认是层级统计,父 cgroup 的值可能包含子 cgroup 的事件。如果服务组里又划分了 worker 子组,父组的 oom 增加不一定发生在父组自己的进程上。要看当前组本地事件,就读取 memory.events.local

Linux cgroup v2 中 memory.events 与 memory.events.local 区分 oom 和 oom_kill 的结构说明图
图2:OOM 事件区分结构图,展示分配失败计数与进程终止计数不是同一个指标。

把判断结果落成排障清单

一次排障至少保留三个时间点:服务开始升高前、计数第一次增加后、恢复或重启后。每次记录 memory.maxmemory.currentmaxoomoom_kill 五个值,并注明读取的是层级文件还是 local 文件。

  • 只有 memory.current 长期接近上限:先检查缓存、批量任务和并发峰值,不要立刻归因于 OOM killer。
  • oom 持续增加:检查申请失败后的错误处理,确认业务是否把 -ENOMEM 当成普通空结果。
  • oom_kill 增加:把进程退出时间与服务重启记录对齐,再决定是降低峰值、提高上限,还是拆分子 cgroup。

字段定义可继续查阅:https://docs.kernel.org/admin-guide/cgroup-v2.html。这个方法的边界也很明确:它解释 cgroup 内存事件计数,不代替宿主机全局内存、swap、内核日志和应用自身指标的联合排查。

相关问题

memory.events 和 memory.events.local 应该读哪个?

要看整个 cgroup 子树的累计影响,读 memory.events;只想判断当前组自己发生了什么,读 memory.events.local。监控父组时最好同时记录二者,避免把子组事件算到错误服务上。

oom 增加但 oom_kill 为零是不是误报?

不是误报。它表示分配曾经到达 OOM 边界,但结果可能是返回分配失败、被调用方忽略,或经过回收与重试后继续运行;只有 oom_kill 增加才确认有进程被杀。

为什么不能只看 memory.current?

memory.current 是当前值,无法告诉你刚才是否碰过上限,也无法区分分配失败和进程终止。事件计数提供了“发生过什么”的历史线索,两者要配合读取。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>