1. OOM 基础概念

1.1 什么是 OOM(Out of Memory)

OOM(Out of Memory)是指 Linux 操作系统在物理内存 + Swap 交换空间全部耗尽,无法满足新的内存分配请求时进入的一种临界资源枯竭状态。当内核检测到系统内存严重不足,且无法通过常规回收手段(如 Page Cache 回收、Swap 换出、Slab 收缩)释放足够内存时,就会触发 OOM 事件。

关键指标:OOM 发生的直接原因是 __alloc_pages_slowpath() 中所有内存回收路径均失败,内核在 out_of_memory() 函数中决定“杀死谁”来释放内存。

OOM 在容器运行时环境中尤为常见,原因包括:

  • cgroup 内存限制:容器进程组被限制在 memory.max 以内,即使宿主机仍有空闲内存,容器内也可能触发 OOM。
  • 内存超卖:多个容器承诺的内存总和超过宿主机物理内存,突发峰值时集体争抢。
  • 内存泄漏:容器内应用存在未释放的内存,持续增长直至触顶。
场景 触发条件 典型表现
宿主机全局 OOM 系统总内存 + Swap 耗尽 内核随机或按 oom_score 杀死进程,可能误杀关键服务
cgroup 内存 OOM 容器 cgroup memory.max 达到上限 仅杀死该 cgroup 内进程,不影响宿主机其他进程
cgroup 内存压力 OOM memory.high 触发频繁回收仍无法满足 进程被 throttle,延迟飙升,最终可能 OOM

1.2 OOM Killer 机制原理

OOM Killer 是 Linux 内核在内存耗尽时启动的最后一道防线。其核心执行流程如下:

  1. 内存分配失败:进程通过 malloc() / mmap() 申请内存,内核在 __alloc_pages_nodemask() 中遍历所有内存区域和回收策略均无法满足。
  2. 进入 OOM 路径:调用 out_of_memory() 函数,检查是否已有 OOM 正在处理(避免并发重复杀进程)。
  3. 选择目标进程:调用 oom_badness() 计算每个进程的 oom_score,选出得分最高的进程。
  4. 发送 SIGKILL:向目标进程发送 SIGKILL 信号(信号 9),强制终止进程并回收其占用的内存。
  5. 内存回收确认:检查被杀进程释放的内存是否足够,若仍不足则继续选择下一个进程。
OOM Killer 触发与执行流程 进程内存分配请求 内存回收路径全部失败 调用 out_of_memory() oom_badness() 计算得分 选择 oom_score 最高进程 发送 SIGKILL 回收内存 关键内核函数 • out_of_memory() • oom_badness() • select_bad_process() OOM 触发条件 • 物理内存 + Swap 耗尽 • cgroup memory.max 触顶 • 内存回收无法满足分配
容器环境差异:在 cgroup v2 中,当容器内存达到 memory.max 时,内核会直接触发 cgroup OOM,仅杀死该 cgroup 内的进程,而不会影响宿主机其他进程。这种行为比宿主机全局 OOM 更可控,也是容器运行时推荐的内存隔离方式。

1.3 OOM Score 与 oom_adj 的关系

OOM Killer 选择“杀谁”的依据是每个进程的 oom_score(OOM 分数),分数越高,被选中的概率越大。内核通过 oom_badness() 函数计算该分数,核心公式如下:

// 内核 oom_badness() 简化逻辑
points = (total_vm / total_ram) * 1000 + oom_score_adj

// 其中:
// total_vm   = 进程虚拟内存总大小(包含共享库、映射文件等)
// total_ram  = 系统总物理内存
// oom_score_adj = 用户可调节的偏移量(-1000 ~ 1000)

用户可以通过 /proc/[pid]/oom_adj 和 /proc/[pid]/oom_score_adj 两个接口来影响进程的 OOM 分数:

接口文件 取值范围 内核版本 说明
/proc/[pid]/oom_adj -17 ~ 15 2.6.11+ 旧接口,-17 表示禁用 OOM Killer(OOM_DISABLE),15 表示最高被杀概率
/proc/[pid]/oom_score_adj -1000 ~ 1000 2.6.36+ 新接口,更精细的调节,-1000 表示禁用 OOM Killer,1000 表示最高概率
/proc/[pid]/oom_score 只读 2.6.11+ 当前进程的 OOM 分数,由内核自动计算

oom_adj 与 oom_score_adj 的映射关系:

// 内核内部转换(fs/proc/base.c)
oom_score_adj = oom_adj * 66 + (oom_adj < 0 ? -66 : 66)

// 示例:
// oom_adj = -17  →  oom_score_adj = -1000  (OOM_DISABLE)
// oom_adj =   0  →  oom_score_adj =    66  (默认值)
// oom_adj =  15  →  oom_score_adj =  1000  (最高被杀概率)
OOM Score 计算与调节机制 oom_badness() 计算 oom_score points = (total_vm / total_ram) × 1000 + oom_score_adj 进程 total_vm(虚拟内存) 系统 total_ram(物理内存) oom_score_adj 调节值 选出 oom_score 最高的进程 → SIGKILL 旧接口 oom_adj 范围 -17 ~ 15,-17 禁用 OOM 新接口 oom_score_adj 范围 -1000 ~ 1000,-1000 禁用 OOM
容器运行时注意事项:在容器环境中,oom_score_adj 的调节需要谨慎。如果容器内进程被设置为 oom_score_adj = -1000(禁用 OOM Killer),当容器内存达到 cgroup 限制时,内核无法杀死该进程,会导致容器内存回收失败,最终可能引发宿主机全局 OOM。容器运行时(如 containerd)默认会对容器 init 进程设置合理的 oom_score_adj 值,不建议用户随意修改。

1.4 关键概念总结

概念 定义 容器调优意义
OOM 内存耗尽状态,内核无法满足新的内存分配 容器内存限制设置不当会频繁触发 OOM
OOM Killer 内核在 OOM 时选择并杀死进程的机制 理解选择逻辑,避免关键容器进程被误杀
oom_score 进程被 OOM Killer 选中的概率分数 分数越高越容易被杀,与内存使用量正相关
oom_adj / oom_score_adj 用户可调节的 OOM 分数偏移量 用于保护关键进程或优先杀死非关键进程