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 内核在内存耗尽时启动的最后一道防线。其核心执行流程如下:
- 内存分配失败:进程通过
malloc()/mmap()申请内存,内核在__alloc_pages_nodemask()中遍历所有内存区域和回收策略均无法满足。 - 进入 OOM 路径:调用
out_of_memory()函数,检查是否已有 OOM 正在处理(避免并发重复杀进程)。 - 选择目标进程:调用
oom_badness()计算每个进程的 oom_score,选出得分最高的进程。 - 发送 SIGKILL:向目标进程发送 SIGKILL 信号(信号 9),强制终止进程并回收其占用的内存。
- 内存回收确认:检查被杀进程释放的内存是否足够,若仍不足则继续选择下一个进程。
容器环境差异:在 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_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 分数偏移量 | 用于保护关键进程或优先杀死非关键进程 |