3. 内存限制与 OOM 机制:--memory 参数详解、OOM Killer 工作原理、oom-score-adj 调整策略

在 Docker 企业级部署中,内存资源限制是防止单容器内存泄漏拖垮宿主机、保障业务连续性的第一道防线。本章深入解析 --memory 参数的实际行为、Linux OOM Killer 的触发与选择逻辑,以及通过 oom-score-adj 实现精细化优先级控制的方法。

3.1 --memory 参数详解:硬限制与软限制

Docker 通过 cgroups 实现内存限制,核心参数为 --memory(或 -m)和 --memory-reservation。两者共同构成内存资源管控的“硬上限”与“软保证”。

参数 类型 默认值 行为说明
--memory / -m 硬限制 无限制 容器进程及子进程使用的内存总量不得超过此值。超过时触发 OOM Killer 或直接拒绝分配(取决于 --oom-kill-disable)。
--memory-reservation 软限制 同 --memory 当宿主机内存充足时,容器可超过此值;当宿主机内存紧张时,系统会优先回收超过此值的内存。用于设置“期望使用量”。
--memory-swap 交换分区限制 两倍 --memory 控制容器可使用的内存+交换分区总量。设为与 --memory 相同可禁用交换。
--memory-swappiness 页面回收倾向 继承宿主机 0~100,值越低越倾向于回收匿名页(进程内存),值越高越倾向于回收文件缓存。
生产建议:始终同时设置 --memory 和 --memory-reservation。硬限制防止失控,软限制帮助调度器在资源竞争时做出合理决策。例如:--memory=4g --memory-reservation=2g。

3.2 OOM Killer 工作原理:从触发到裁决

当容器内存使用达到 --memory 硬限制时,Docker 不会直接杀死容器,而是触发 Linux 内核的 OOM Killer 机制。理解其完整流程是排查内存异常的基础。

OOM Killer 触发与裁决流程 容器内存达到硬限制 内核触发 OOM Killer 计算每个进程 oom_score 选择 oom_score 最高进程 发送 SIGKILL 杀死进程 容器退出(Exit Code 137) 关键指标:容器退出码 137 表示被 SIGKILL(9)杀死,128+9=137
核心公式:oom_score = 已用内存比例 × 系数 + oom_score_adj
内核通过此公式为每个进程打分,得分最高的进程被优先杀死。已用内存比例越高、oom_score_adj 越大,被杀死的概率越高。

3.3 oom-score-adj 调整策略:精细化优先级控制

oom-score-adj 是 Docker 提供的用户空间接口,允许运维人员直接干预 OOM Killer 的选择结果。其取值范围为 -1000 到 1000,默认值为 0。

取值区间 效果 典型场景
-1000 完全禁用 OOM Killer 对该进程的杀伤 关键基础设施进程(如监控 Agent、SSH 守护进程)
-500 ~ -100 显著降低被杀死概率 数据库主进程、消息队列 Broker
0 默认行为,按内存使用比例公平竞争 普通无状态应用
100 ~ 500 增加被杀死概率 缓存服务、批处理任务(可容忍中断)
1000 确保该进程被优先杀死 临时计算任务、低优先级日志处理
避坑指南:不要对容器内所有进程统一设置 oom-score-adj。Docker 默认只对容器主进程(PID 1)生效。如果容器内运行多进程(如 Java 应用 + Sidecar),需要借助 docker run --oom-score-adj 配合 init 进程或自定义启动脚本,将调整值应用到子进程。

3.4 实战:内存限制与 OOM 调整配置示例

以下示例展示如何为一个 Java 微服务容器配置内存限制和 OOM 优先级:

# 启动容器:硬限制 2G,软限制 1G,禁用交换,降低 OOM 优先级
docker run -d \
  --name payment-service \
  --memory=2g \
  --memory-reservation=1g \
  --memory-swap=2g \
  --oom-score-adj=-200 \
  -e JAVA_OPTS="-Xmx1536m -Xms512m" \
  payment-service:1.2.3

# 验证配置
docker inspect payment-service --format='{{.HostConfig.Memory}}'
# 输出:2147483648(2GB)

# 查看容器内进程的 oom_score_adj
docker exec payment-service cat /proc/1/oom_score_adj
# 输出:-200
最佳实践:Java 应用设置 -Xmx 时,建议保留 10%~20% 的余量给 JVM 堆外内存(Metaspace、线程栈、Direct Buffer)。例如 --memory=2g 时,-Xmx 设为 1536m~1600m,避免因堆外内存超限触发 OOM。

3.5 排查 OOM 异常的常用命令

当容器因 OOM 退出时,以下命令可快速定位根因:

  • 查看容器退出码:docker inspect --format='{{.State.ExitCode}}' <container> —— 137 表示 OOM 杀死。
  • 查看 OOM 事件:docker events --since 5m | grep -i oom —— 实时或历史 OOM 事件。
  • 查看内核日志:journalctl -k --since "5 minutes ago" | grep -i "oom\|killed" —— 查看具体被杀死进程。
  • 查看 cgroup 内存统计:docker exec <container> cat /sys/fs/cgroup/memory/memory.stat —— 分析内存使用构成。
  • 查看 OOM 调整值:docker inspect --format='{{.HostConfig.OomScoreAdj}}' <container> —— 确认配置是否生效。
快速诊断口诀:退出码 137 → 查 OOM 事件 → 看内核日志 → 调 oom-score-adj 或增大 --memory。