核心在于让JVM主动感知容器内存限制,否则即使Compose设memory:1G,JVM仍可能按宿主机内存默认申请2GB堆,触发OOM Killer终止(退出码137);需在Java 8u191+/10+中启用-XX:+UseContainerSupport(旧版还需-XX:+UnlockExperimentalVMOptions),并配合deploy.resources.limits.memory硬限制,二者缺一不可。

核心在于让 JVM 主动感知容器内存限制,而不是按宿主机总内存估算堆大小——否则即使 Compose 设了 memory: 1G,JVM 仍可能默认申请 2GB 堆,一启动就触发 OOM Killer 终止进程(退出码 137)。
让 JVM 真正“看得到”容器内存限制
Java 8u191+ 和 Java 10+ 默认启用容器感知支持,但必须显式开启关键参数:
- -XX:+UseContainerSupport:启用 cgroup 内存/CPUs 检测(必需,旧版默认关闭)
- -XX:+UnlockExperimentalVMOptions:Java 8u191~u212 需要解锁实验选项
- -Xmx 建议设为容器 memory limit 的 50%–70%,预留空间给元空间、直接内存、线程栈等非堆区域
例如,在 docker-compose.yml 中为 Java 服务配置:
- JAVA_TOOL_OPTIONS=-XX:+UseContainerSupport -XX:+UnlockExperimentalVMOptions -Xmx700m
deploy:
resources:
limits:
memory: 1G
Compose 层必须配对设置硬限制
只调 JVM 参数不设容器限制,等于没锁门;只设限制不调 JVM,等于门锁了但屋里人乱砸墙。二者缺一不可:
-
deploy.resources.limits.memory是硬边界,超限即被 OOM Killer 杀死 - 避免使用
--memory-swap(尤其在 CentOS 7.9 上),它会干扰 cgroup v1 的内存统计精度 - 慎用
mem_reservation作软限制——它不防 OOM,仅用于调度器预估,不能替代 limits
真实案例中,某 Spring Boot 微服务在 Dell R7525 服务器上频繁重启,查 docker inspect 显示 "OOMKilled": true,最终发现是 Compose 里漏写了 limits,仅靠 JVM 参数无法阻止内核介入。
验证是否真正生效的三步检查法
部署后立即执行以下命令交叉验证:
- 进容器运行
java -XX:+PrintFlagsFinal -version | grep MaxHeapSize—— 输出值应接近你设的-Xmx,而非宿主机总内存的 1/4 - 查 cgroup 实际限制:
cat /sys/fs/cgroup/memory/memory.limit_in_bytes—— 应与 Compose 中memory: 1G一致(约 1073741824) - 观察日志是否出现
OpenJDK Server VM warning: INFO: os::commit_memory failed—— 出现说明 JVM 尝试突破 cgroup 限制,参数未生效
配套规避常见陷阱
很多崩溃不是因为没调优,而是踩了隐性坑:
- 镜像基础层别用
openjdk:17-jdk,改用openjdk:17-jre-slim—— 减少类加载开销与镜像体积,降低冷启动内存峰值 - 禁用不必要的 JVM 特性,如
-XX:+UseG1GC在小堆场景下反而增加元空间压力,可改用-XX:+UseSerialGC - Spring Boot 应用加上
spring.application.name和management.endpoint.health.show-details=always,便于通过/actuator/health快速确认 JVM 是否已正确识别容器环境


















