Linux 内核有个 OOM (Out of Memory) Killer 的机制,可以在系统内存不足的时候,通过主动杀死一些进程来释放更多的内存空间。

很多时候,可以 ping 通一台服务器,但无法 ssh 上去,因为 sshd 被 OOM Killer 杀掉了。ping 能 ping 通,是因为处在内核态协议栈还能工作,发出回送报文。sshd 则因为是用户态进程,直接被干掉了。

OOM Killer 工作流程

OOM Killer 的完整工作流程如下:

graph TD
    A[系统内存不足] --> B{是否触发 OOM?}
    B -->|是| C[遍历所有进程]
    B -->|否| Z[继续正常运行]
    C --> D[计算每个进程的 oom_score]
    D --> E[应用 oom_score_adj 调整]
    E --> F[选择分数最高的进程]
    F --> G{是否启用 OOM Killer?}
    G -->|否| H[系统挂起/panic]
    G -->|是| I[发送 SIGKILL 信号]
    I --> J[进程被杀死]
    J --> K[释放内存资源]
    K --> Z

关键机制

oom_score

每个进程都有一个 oom_score 值,范围是 0 到 1000,分数越高越容易被 OOM Killer 选中。可以通过 /proc/PID/oom_score 查看。

oom_score 的计算主要考虑以下因素:

  1. 进程占用的内存大小:包括虚拟内存、物理内存、swap 空间等
  2. 进程的运行时间:运行时间长的进程分数较低
  3. 进程的优先级:nice 值会影响分数
  4. 进程的 root 权限:root 进程会有一定的保护
  5. 是否直接访问硬件:直接访问硬件的进程分数会降低

oom_score_adj

oom_score_adj 是调整进程 oom_score 的接口,范围是 -1000 到 1000。可以通过 /proc/PID/oom_score_adj 进行设置。

  • -1000:完全禁止 OOM Killer 杀死该进程(相当于旧的 oom_adj = -17)
  • 0:默认值,不调整
  • 1000:极大增加被杀死的概率

旧的 oom_adj 接口(范围 -17 到 15)已被废弃,新代码应使用 oom_score_adj

vm.overcommit_memory

vm.overcommit_memory 参数控制内核的内存过度分配策略,位于 /proc/sys/vm/overcommit_memory

  • 0:启发式过度分配(默认值),内核会根据实际情况决定是否允许过度分配
  • 1:总是允许过度分配,可能导致 OOM
  • 2:严格过度分配控制,不允许过度分配超过 swap + 可配置的 RAM 比例

控制 OOM Killer

关闭再打开 OOM Killer

1
2
echo "0" > /proc/sys/vm/oom-kill
echo "1" > /proc/sys/vm/oom-kill

保护特定进程

可以通过设置 oom_score_adj 为 -1000 来强制内核不得杀死某个进程:

1
pgrep -f "/usr/sbin/sshd" | while read PID;do echo -1000 > /proc/$PID/oom_score_adj;done

或者针对单个进程:

1
echo -1000 > /proc/PID/oom_score_adj

cgroup 与 OOM Killer

在容器化环境中,cgroup 提供了更精细的 OOM 控制机制。

cgroup v1

在 cgroup v1 中,memory subsystem 提供了以下控制:

  • memory.limit_in_bytes:设置内存限制
  • memory.oom_control:控制 OOM 行为
    • 设置为 0 时禁用该 cgroup 的 OOM Killer
    • 设置为 1 时启用 OOM Killer

cgroup v2

在 cgroup v2 中,OOM 控制更加统一:

  • memory.max:设置内存限制
  • memory.oom.group:控制是否杀死整个进程组
    • 设置为 0 时只杀死触发 OOM 的进程
    • 设置为 1 时杀死整个 cgroup 中的所有进程

排查 OOM 问题

查看 OOM 日志

1
2
3
4
5
6
7
8
# 使用 dmesg 查看
dmesg | grep -i "killed process"

# 查看系统日志
grep -i "out of memory" /var/log/messages

# 使用 journalctl(systemd 系统)
journalctl -k | grep -i "oom"

典型的 OOM 日志如下:

1
2
Out of memory: Kill process 1234 (java) score 900 or sacrifice child
Killed process 1234 (java) total-vm: 12345678kB, anon-rss: 9876543kB, file-rss: 123456kB

监控内存使用

1
2
3
4
5
6
7
8
# 查看系统内存使用情况
free -h

# 查看进程内存使用排序
ps aux --sort=-%mem | head -n 10

# 查看 cgroup 内存使用(容器环境)
cat /sys/fs/cgroup/memory/memory.usage_in_bytes

预防措施

  1. 合理设置 swap:适当的 swap 空间可以缓解内存压力
  2. 监控内存使用:使用监控工具及时发现内存异常
  3. 优化程序:减少内存泄漏和不必要的内存占用
  4. 设置合理的 limits:使用 ulimit 或 cgroup 限制进程内存
  5. 配置 OOM 保护:对关键进程设置 oom_score_adj = -1000

参考文档:

  1. 关于OOM-killer
  2. 这里有一篇《OOM Killer》讨论它的工作细节。
  3. Linux 下 OOM Killer 机制的详解
  4. Linux内核OOM机制的详细分析