先记住这个答案
当容器内所有进程的内存使用总量超过-m限制时,内核内存cgroup控制器触发OOM killer,选取一个进程发送SIGKILL以回收内存。若被杀进程是容器主进程,容器退出并返回137。验证方法包括:docker inspect查看State.OOMKilled为true且ExitCode为137,或在宿主机运行dmesg查看内核日志中的oom-killer记录。注意容器可能不退出如果主进程存活,但一般会优先杀死占用内存最高的进程。
- 内存超限触发cgroup OOM killer,主进程被杀则退出137。
- 用
docker inspect的OOMKilled字段确认。 - 内核日志中可查cgroup OOM记录。
cgroup内存上限与OOM killer触发机制
进程申请内存时,内核的memory cgroup会检查当前cgroup已用内存加上新申请是否超出limit_in_bytes。若超出且无法通过回收足够内存,内核会触发OOM事件,实质是调用out_of_memory()并遍历该cgroup的所有进程,选择oom_score最高的进程执行SIGKILL。Docker的-m直接设置cgroup v1的memory.limit_in_bytes或v2的memory.max。
OOM killer选择进程时会参考进程的oom_score_adj,默认Docker不调整容器内进程的该值,因此消耗内存最多的进程通常先被杀。被杀进程收到SIGKILL后直接终止,如果该进程恰好是PID 1(主进程),容器就会停止并返回128+9=137的退出码。如果被杀的只是子进程,容器可能还活着,但可能处于异常状态。
Java内存容器被OOM的实际场景
假设一个Java服务在容器内运行,启动参数-Xmx512m,但容器只设置了-m=512m。JVM默认堆外内存可能占用200MB,导致总内存很快逼近512MB。当JVM尝试分配新对象时,cgroup检测到超限,OOM killer选中JVM进程并杀之,容器随即退出,docker inspect显示OOMKilled: true和ExitCode: 137。
为避免此问题,应综合计算JVM堆与堆外内存,设置-m=1g等更合理值。也可使用--oom-kill-disable但必须与-m同时设置,否则宿主机危险。此时超限不会杀进程,但会导致不必要的系统和Swap压力,因此生产环境更建议调整应用与限制。
限制边界与验证陷阱
cgroup OOM只影响该容器,不会直接波及宿主机其他进程;但宿主全局内存耗尽会触发系统级OOM,可能杀死Docker daemon。此外-m限制包含文件缓存,当容器大量读文件导致page cache增长时也可能触发OOM,即使匿名内存不高。这种场景属于内核需要回收内存却无法及时压缩,容易误判。
验证时注意docker inspect的OOMKilled字段仅在当前容器运行期有效,如果容器自动重启(如--restart=always)则状态被重置,应依赖内核日志,例如dmesg中出现“Memory cgroup out of memory: Killed process”字样,是可靠依据。
容易答错的地方
- 认为容器内所有进程都会被杀死
- 错误。OOM killer只选一个进程,通常是最耗内存的。如果主进程恰好不是最高分,容器可能继续运行,但功能可能受损。实际多数场景主进程就是最大占用者。
- 认为docker stats内存达标就立即OOM
- 错误。
docker stats以1秒间隔采集,可能滞后;且触发OOM需要内核在申请内存时判定,存在瞬时性。正确验证依赖OOMKilled字段和内核日志。
面试官还会怎么问?
如何调整容器内进程被OOM killer选中的优先级?
可用--oom-score-adj调整整容器的OOM分数偏移,或对容器内进程调/proc/pid/oom_score_adj。但官方不推荐设置极端负值,以免系统无法保护关键进程。
`--oom-kill-disable`有什么使用前提?
必须与-m同时使用,否则当宿主内存不足时内核可能尝试杀宿主机进程。启用后容器超限不会被杀,但可能导致系统内存压力持续和swap浪费,官方对此有警告。
如何区分cgroup OOM与宿主机系统级OOM?
查看内核日志:cgroup OOM包含“Memory cgroup out of memory”,系统OOM包含“Out of memory: Kill process”。容器没有独立内核,日志在宿主机。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。