docker 容器 OOMKilled 速查:exit 137 与内存限额处置
更新时间:2026-10-24。FAQ 速查页,面向容器「莫名其妙就退出」、
docker ps -a里显示Exited (137)的同学。docker OOMKilled 的本质一句话:容器内存用量顶到了 cgroup 限额,内核 OOM killer 直接挑中主进程 SIGKILL 掉——没有日志、没有收尾、应用自己都不知道自己死了。137 = 128 + 9(SIGKILL 的信号号),看到这个退出码,第一反应就该是查内存。病灶收敛在六根轴上:-m限额本身给小了、JVM 等运行时按宿主机内存算堆、应用真实内存泄漏、容器内多进程叠加超支、swap 与限额语义误解、宿主机全局 OOM 殃及容器。进程收到段错误(退出码 139)是另一条线,看 docker 容器段错误速查;进程活着但不干活看 docker 容器卡死速查。
本文要回答的问题
Exited (137)与OOMKilled=true哪个才算内存被杀的铁证?- 明明没设
-m限额,为什么容器还是被 OOM 杀了? - JVM 的
-Xmx给了 1G,容器限额也 1G,为什么还是被杀? - 怎么确认是「限额太小」还是「应用真的漏内存」?
- dmesg 里的哪行日志能坐实 OOM kill 这件事?
一、报错原文与典型现象
OOMKilled 的「报错」不在应用日志里,在 Docker 状态与内核日志里(路径以 /proj/demo 占位):
# 铁证一:退出码 137 + OOMKilled 标记
$ docker ps -a --filter name=demo-app
CONTAINER ID IMAGE STATUS NAMES
7c2b9e4a1d05 myapp Exited (137) 5 minutes ago demo-app
$ docker inspect demo-app --format '{{.State.OOMKilled}} {{.State.ExitCode}}'
true 137# 铁证二:内核日志里的 oom-kill 记录(宿主机上执行)
$ dmesg -T | grep -i "killed process" | tail -2
[Sat Oct 24 10:01:12 2026] Memory cgroup out of memory: Killed process 18234 (java) total-vm:5242880kB, anon-rss:1048576kB# 反例:137 但 OOMKilled=false —— 不是内存问题,是被 SIGKILL 强杀
$ docker inspect demo-app --format '{{.State.OOMKilled}} {{.State.ExitCode}}'
false 137读现象抓三条判据:OOMKilled=true 才是内存被杀的铁证,137 单独出现也可能是 docker kill、stop 宽限期超时强杀、或宿主机全局 OOM;dmesg 里 Memory cgroup out of memory 指名是容器限额触发,而普通的 Out of memory: Killed process(不带 memory cgroup 字样)是宿主机全局内存耗尽;被杀进程名(上例 java)与 RSS 数值直接告诉你凶手是谁、死前吃了多少。
二、病因处置速查表
| 报错现象 / 场景 | 可能原因 | 快速验证 | 处置 |
|---|---|---|---|
| Exited 137 且 OOMKilled=true | 容器内存顶到 -m 限额 | inspect 看 OOMKilled 与 limit | 按需加限额或优化内存 |
| 137 但 OOMKilled=false | docker kill / stop 超时强杀 | 查操作记录与 stop 耗时 | 走卡死链路排查信号 |
| JVM 容器堆明明不大却被杀 | 堆外内存 + JVM 未感知限额 | 看 JDK 版本与 -Xmx 推算 | 升 JDK8u191+ 或设 MaxRAM |
| 跑几小时才被杀 | 内存泄漏缓慢爬升 | docker stats 连续采样 | 抓堆 dump / pprof 定位泄漏 |
| 峰值时偶发被杀 | 多进程叠加瞬时超支 | stats 看峰值 MEM USAGE | 限额按峰值 + 裕量配 |
| 宿主机也卡、多容器连环死 | 宿主机全局 OOM | dmesg 无 memory cgroup 字样 | 宿主机加内存或挪容器 |
| 设了 swap 仍快速被杀 | swap 限额语义误解 | inspect 看 MemorySwap | memory-swap = 2x memory 才生效 |
| Java 堆内 OOM 报错退出 | 这是 JVM 自己的 OOMError | 日志有 OutOfMemoryError | 调 -Xmx 或修泄漏,与 cgroup 无关 |
三、排查决策图
这张图是标准排查路线:红是 OOM 现象与命中的根因,蓝是排查动作,绿是验证通过,橙是修复,紫是防复发沉淀。
读这张图抓住第一刀:先 inspect .State.OOMKilled 把「真 OOM」和「被强杀」分开——false 就别在内存上浪费时间,转去查信号链(参考容器卡死篇的 SIGKILL 链路);true 再用 dmesg 区分「容器自己的 cgroup 限额到了」还是「宿主机全局耗尽」,两者的处置一个改容器配置、一个动宿主机容量,完全两条路。
四、逐条排查展开
限额触发的 OOM 是绝大多数。 容器跑了 -m 512m 或 compose 的 mem_limit,cgroup 统计的内存(含 page cache 的可回收部分算法随内核版本而异,anon RSS 是大头)顶到线,内核就从容器里挑「最该死」的进程 SIGKILL。确认与复核:
docker inspect demo-app --format '{{.HostConfig.Memory}} {{.State.OOMKilled}}'
docker stats demo-app --no-stream # 被杀前用持续采样看爬升曲线处置不是无脑加限额:先用 docker stats 连续采样标定应用真实峰值,限额按「峰值 × 1.3」给;峰值本身离谱(一个 API 服务吃 2G)就该回头审应用。
JVM 不感知容器限额是 Java 容器化的经典坑。 JDK8u131 之前的 JVM 按宿主机物理内存比例算默认堆——64G 宿主机上默认堆上限能算出 16G,容器限额只给 1G,结局注定是被杀。处置按版本走:JDK8u191+/JDK10+ 默认开启容器感知(-XX:+UseContainerSupport),按 cgroup 限额算堆;更老版本显式 -XX:MaxRAMFraction=1 配合 -Xmx 钉死,或直接升级 JDK。注意堆不是全部:堆外内存(Netty 的 direct buffer、JNI、线程栈、metaspace)照样吃限额,-Xmx 给到限额的 60–70% 是安全水位,别顶格。
「跑几小时才被杀」多半是泄漏而不是限额小。 限额不够的死法通常是启动不久或压力一来就死;缓慢爬升、几小时到几天才触线的曲线指向泄漏。分辨靠采样:watch docker stats 或在监控系统里看 MEM USAGE 是否单调爬升、回落不干净。确认后按语言上工具——Java 堆 dump 用 MAT 看支配树,Go 用 pprof 的 heap 页,Node 用 heapdump;native 进程(C/C++)用 valgrind 或 ASan,检测方法见 Valgrind 内存检测 等崩溃排查簇文档。
多进程叠加与瞬时峰值容易被低估。 容器里跑了主进程 + sidecar + 定时任务脚本,各自单独看都不大,同一时刻叠加(比如凌晨备份脚本与业务高峰撞上)瞬间超支。排查时把 docker top 的进程清单与 stats 峰值对齐看;处置要么错峰,要么把重任务拆到独立容器各自限额。编排层的 kubectl top pod 与 requests/limits 同理——limits 给小了,k8s 里的表现就是容器反复 OOMKilled 重启。
宿主机全局 OOM 是另一层病。 dmesg 里是 Out of memory: Killed process(无 memory cgroup 字样)、且同一时间多个容器甚至宿主机进程被杀,说明宿主机物理内存真没了,内核在全局范围挑进程杀。这时给单个容器加 -m 反而让它死得更快(cgroup 限额先于全局触发);正路是宿主机加内存、挪走部分容器、或给关键容器设 oom-score-adj 压低被全局 killer 选中的优先级。
swap 的语义别踩。 --memory-swap 是「内存 + swap 的总限额」,不是 swap 本身的大小:-m 512m --memory-swap 1g 才有 512m 的 swap 可用;只设 -m 不设 memory-swap 时默认同值,等于没有 swap。指望 swap 兜底扛峰值却发现照样被杀,先核对这两个值的关系。但要清醒:swap 只是延缓死亡,命中 swap 后性能塌方式下降,生产内存型服务不该依赖它。
五、规范做法与预防
- 限额必配且按实测给:所有容器显式
-m,数值来自压测峰值 × 1.3 裕量,禁止裸奔。 - JVM 容器感知三件套:JDK8u191+ 或显式
-XX:MaxRAMPercentage=70,堆外水位单独核算。 - 监控盯比率不盯绝对值:MEM USAGE / LIMIT 超 80% 告警,爬升曲线单调不回落直接报泄漏嫌疑。
- 重启策略配退路:OOM 型退出配
restart: on-failure加告警,别让容器无声死循环重启。 - 压测含内存稳态用例:满载跑足够时长,确认曲线收敛而非单调爬升。
- dmesg 进巡检:宿主机定期收集 oom-kill 记录,与容器清单对账,不漏全局 OOM。
这一篇在知识体系里的位置
本篇是 docker 排错速查簇的内存限额篇,定位「进程被内核杀掉、应用来不及留遗言」:退出码 139(SIGSEGV)的崩溃看 docker 容器段错误速查;被强杀但 OOMKilled=false 的信号链路看 docker 容器卡死速查;缺库、路径、权限等启动期死因看 docker 容器缺动态库速查 与 docker 权限被拒速查;内存泄漏的检测工具链属崩溃排查簇。OOMKilled 辨认、限额按峰值标定、JVM 容器感知,是容器内存治理的通用底盘。
一句话总结
docker 容器退出先看 inspect .State.OOMKilled:false 是被 SIGKILL 强杀转去查信号链,true 才走内存线;再用 dmesg 分「Memory cgroup out of memory」(容器 -m 限额触发,占绝大多数)与全局 OOM(宿主机内存耗尽,加容器限额反而死更快)。限额侧的标准动作是 stats 压测标定峰值 × 1.3 给限额;Java 容器确认 JDK8u191+ 容器感知或显式 MaxRAMPercentage=70,堆外内存留水位;缓慢爬升几小时才死是泄漏不是限额小,抓 dump 定位。监控盯 MEM USAGE/LIMIT 比率与爬升形态,dmesg 定期对账,OOMKilled 即可从「神秘猝死」变成可预期事件。