容器跑着跑着突然消失docker ps 里找不到了应用日志最后一行还停在正常的业务输出上没有任何报错——这是 OOMOut Of Memory被杀的典型表现。它和普通崩溃不一样进程是被内核直接 SIGKILL 的来不及写日志、来不及清理所以现场特别干净干净到让人摸不着头脑。这篇讲清楚三件事怎么确认容器是被 OOM 杀的、为什么会 OOM宿主 OOM 和 cgroup OOM 是两回事、以及怎么用内存限制参数和 JVM 配置从根上防住。第一步确认是不是 OOM看容器退出状态docker ps -a --filter statusexited # 或针对具体容器 docker inspect 容器名 --format {{.State.Status}} {{.State.ExitCode}} OOMKilled{{.State.OOMKilled}}输出exited 137 OOMKilledtrue两个关键信号ExitCode 137 128 9即收到 SIGKILLOOMKilledtrue Docker 明确标记这个容器是因内存超限被杀如果 OOMKilledtrue基本可以定性为cgroup OOM容器自己的内存限制被触发。如果 OOMKilledfalse 但 ExitCode 137可能是被人 docker kill 了或者宿主机级别 OOM见下。看内核日志确认宿主机 OOMdmesg -T | grep -i oom\|killed process | tail输出类似[Wed Sep 24 14:32:11 2026] Memory cgroup out of memory: Killed process 12345 (java) total-vm:4194304kB ... [Wed Sep 24 14:32:11 2026] Out of memory: Killed process 6789 (java) ...出现 Memory cgroup out of memory → cgroup OOM容器限制触发出现 Out of memory: Killed process没有 cgroup 字样→宿主机全局 OOM内核在整机内存不足时挑进程杀可能杀到没设限制的容器这两者的处理思路完全不同务必先分清。两种 OOM 的根因cgroup OOM容器限制触发容器设置了 --memory或 Compose 的 mem_limit当容器内存用量超过限制内核的 cgroup OOM killer 杀掉容器内进程。常见原因限制设得太低应用正常负载就需要更多应用内存泄漏缓慢增长直到撞线JVM 堆外内存Metaspace、DirectByteBuffer、线程栈超出预期堆没满但总内存超了宿主机 OOM整机内存不足容器没设限制或限制总和超过物理内存多个容器一起把宿主机内存吃满内核全局 OOM killer 出手按 oom_score 挑进程杀——往往杀掉内存占用最大的那个可能根本不是肇事者。# 看宿主机内存现状 free -h # 看各容器实时内存 docker stats --no-streamdocker stats 输出CONTAINER ID NAME MEM USAGE / LIMIT MEM % a1b2c3d4e5f6 app 1.8GiB / 2GiB 90.00% d4e5f6a7b8c9 mysql 900MiB / 4GiB 21.48%MEM USAGE / LIMIT 里 LIMIT 显示的是该容器的内存上限没设限制时显示宿主机总内存。MEM % 持续在 90% 以上的容器就是高危对象。内存限制参数详解--memory-m硬上限。容器内存用量超过即触发 cgroup OOM。docker run -d --memory 2g --name app myapp:1.0--memory-swap内存 swap 的总上限。默认等于 --memory 的 2 倍即允许等量 swap。设为与 --memory 相同则禁用 swap设为 -1 表示 swap 不限。# 内存 2g完全禁用 swap docker run -d --memory 2g --memory-swap 2g app # 内存 2g额外允许 1g swap总 3g docker run -d --memory 2g --memory-swap 3g app生产环境建议 --memory-swap 等于 --memory禁 swap避免容器用 swap 导致性能抖动且掩盖真实内存压力。--memory-reservation软限制。内存紧张时才生效用于调度参考不强制。--oom-score-adj调整容器被宿主机全局 OOM killer 选中的优先级范围 -1000~1000。值越高越容易被杀。关键容器可以调低docker run -d --oom-score-adj -500 --name critical-db mysql:8.4--oom-kill-disable禁止杀该容器仅 cgroup OOM。慎用——容器超限时不杀进程会导致内存一直涨最终拖垮宿主机。一般只在搭配 --memory 且你明确知道后果时用。对已运行的容器改限制不用重建docker update --memory 4g --memory-swap 4g 容器名Compose 里的写法v2services: app: image: myapp:1.0 deploy: resources: limits: memory: 2g注意 deploy.resources.limits 在 docker compose up非 swarm下也生效。老写法 mem_limit: 2g 仍可用但推荐 deploy 块。Java 应用的特殊坑JVM 不认容器限制这是 Java 容器 OOM 的头号原因。JDK 8 早期版本8u131 之前不感知 cgroup 内存限制JVM 按宿主机物理内存计算默认堆大小。比如宿主机 32GJVM 默认堆可能开到 8G而容器限制只有 2G——堆还没到 8G容器总内存先撞 2G 上限cgroup OOM 杀掉进程。解法一升级 JDK 并启用容器感知。JDK 8u191 / 10 默认开启 UseContainerSupportJVM 会读 cgroup 限制。确认java -XX:PrintFlagsFinal -version 21 | grep UseContainerSupport # 期望bool UseContainerSupport true解法二用 MaxRAMPercentage 代替固定 -Xmx。让堆按容器内存限制的百分比分配而不是写死java -XX:MaxRAMPercentage75.0 -jar app.jar容器限制 2G 时堆上限约 1.5G剩下 0.5G 留给 Metaspace、线程栈、DirectBuffer 等堆外内存。不要设 100%堆外内存没地方放照样 OOM。75% 是常用值。解法三显式 -Xmx 但要算上堆外。如果坚持写死# 容器限制 2g堆设 1.2g留 0.8g 给堆外 java -Xmx1280m -jar app.jar排查堆外内存如果堆没满但容器 OOM用 Native Memory Tracking 看分布# 启动时加 java -XX:NativeMemoryTrackingsummary -jar app.jar # 运行中查看 jcmd pid VM.native_memory summary输出会列出 Heap、Class(Metaspace)、Thread、Code、GC、Internal 等各区域占用定位是 Metaspace 涨类加载泄漏还是 Thread 涨线程创建过多还是 Internal 涨DirectByteBuffer。完整排查流程一次真实的 OOM 排查过程# 1. 确认死因 docker inspect app --format {{.State.ExitCode}} {{.State.OOMKilled}} # → 137 true ⇒ cgroup OOM # 2. 看限制是多少 docker inspect app --format {{.HostConfig.Memory}} # → 2147483648 (2g) # 3. 看内核日志佐证 dmesg -T | grep -i memory cgroup | tail -3 # 4. 重启容器后监控增长曲线 docker stats app # 观察 MEM USAGE 是否持续单调上涨泄漏特征还是平稳限制过低特征 # 5. 进容器看 JVM 内存分布Java 应用 docker exec -it app jcmd 1 VM.native_memory summary docker exec -it app jmap -heap 1 # 6. 定性 # - 平稳但接近上限 ⇒ 限制过低调大 --memory 或调小 -Xmx/MaxRAMPercentage # - 单调上涨不回落 ⇒ 内存泄漏dump 堆分析 docker exec app jmap -dump:live,formatb,file/tmp/heap.hprof 1 docker cp app:/tmp/heap.hprof ./heap.hprof # 拷出来用 MAT/JVisualVM 分析泄漏 vs 限制过低的判别docker stats 里内存曲线平稳波动 限制过低或堆外配置不当持续单调上涨不回落 泄漏。这个判别能省掉一大半排查时间。防住 OOM 的四条配置每个容器都设 --memory不设限制的容器在宿主机 OOM 时是随机牺牲品设了限制至少死得可控、可预期--memory-swap 等于 --memory禁 swap避免性能抖动和掩盖内存压力Java 用 -XX:MaxRAMPercentage75让堆自适应容器限制留出堆外空间监控告警对 docker stats 的 MEM % 或 cgroup 的 memory.current 设 80% 告警在 OOM 之前发现趋势Prometheus 场景可以抓 cgroup 指标# cgroup v2 路径 cat /sys/fs/cgroup/system.slice/docker-容器id.scope/memory.current cat /sys/fs/cgroup/system.slice/docker-容器id.scope/memory.maxmemory.current / memory.max 持续 0.8 就该扩容或排查了。小结容器 OOM 分两种cgroup OOMOOMKilledtrue容器限制触发和宿主机 OOM内核全局 killer可能误杀。确认用 docker inspect 的 ExitCode 137 OOMKilled 字段和 dmesg。防住靠三点给每个容器设 --memory 且 --memory-swap 同值禁 swapJava 应用用 MaxRAMPercentage 让堆感知容器限制并留堆外空间用 docker stats 曲线区分限制过低和内存泄漏前者调参后者 dump 堆。记住OOM 现场干净不是没原因是进程被 SIGKILL 来不及说话。学会从 OOMKilled 字段和 dmesg 里读出现场这类问题就不再是玄学。