核心是让JVM感知容器内存限制以合理分配堆与非堆资源,否则易因堆超限触发OOM Killer;需启用UseContainerSupport、动态设置堆大小、防控元空间/直接内存溢出,并配置堆转储与GC日志用于诊断。

核心是让JVM“看得到”容器的内存限制,并据此合理分配堆与非堆资源,而不是按宿主机规格盲目配置。否则即使容器设了512MB内存限制,JVM仍可能按默认规则申请2GB堆,触发OOM Killer强制杀进程。
启用容器感知支持(必须第一步)
JDK 8u191+ 和 JDK 10+ 默认开启 -XX:+UseContainerSupport,但旧版本或镜像中常被禁用。若未启用,JVM无法读取 /sys/fs/cgroup/memory/memory.limit_in_bytes,会把宿主机总内存当作可用上限。
- 确认是否生效:进入容器执行
java -XX:+PrintFlagsFinal -version | grep UseContainerSupport,输出为true表示已启用 - 显式启用(兼容性保障):在启动参数中加入
-XX:+UseContainerSupport - 配合使用
-XX:+UseCGroupMemoryLimitForHeap(JDK 8u131+),让JVM自动将堆大小设为 cgroup 内存限制的 1/4(可调)
合理设置堆内存比例(避免硬编码)
不要写死 -Xmx512m,而应依赖容器限制动态推导。例如容器限制为1GB,JVM堆建议设为512–768MB(50%–75%),留出空间给元空间、直接内存、线程栈等非堆区域。
- 推荐组合(JDK 17+):
-Xms${MEM_LIMIT_MB}m -Xmx${MEM_LIMIT_MB}m -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=256m - 更稳妥做法:用脚本从
/sys/fs/cgroup/memory.max读取限制值并计算,如MEM_LIMIT_KB=$(cat /sys/fs/cgroup/memory.max); HEAP_KB=$((MEM_LIMIT_KB * 60 / 100)); java -Xmx${HEAP_KB}k ... - 禁用堆自适应(避免抖动):
-XX:-UseAdaptiveSizePolicy,防止GC过程中反复调整新生代大小
重点防控非堆内存溢出
容器OOM崩溃常不来自堆,而是元空间(Metaspace)或直接内存(DirectBuffer)。尤其Spring Boot微服务加载大量自动配置类、动态代理、NIO连接时极易触达上限。
- 元空间:加
-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m,避免频繁扩容;若用GraalVM Native Image可彻底规避 - 直接内存:Netty、Druid、Tomcat NIO等默认使用DirectBuffer,限制其总量:
-XX:MaxDirectMemorySize=128m - 线程栈:高并发下线程数暴涨,每个线程默认1MB栈空间。可减小:
-Xss256k(需验证无StackOverflow)
配置OOM现场捕获与快速诊断
崩溃后没有堆转储,就等于没留下线索。必须在容器启动时预埋诊断能力。
- 必加参数:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heap.hprof -XX:+ExitOnOutOfMemoryError - 挂载宿主机目录保存dump:
docker run -v $(pwd)/dumps:/tmp/dumps -e JAVA_OPTS="-XX:HeapDumpPath=/tmp/dumps/" ... - 同时开启GC日志便于分析回收行为:
-Xlog:gc*:file=/tmp/gc.log:time,tags,level - 容器退出后立即用
docker cp拷出 dump 文件,用 MAT 分析“Dominator Tree”定位大对象


















