堆转储文件必须写入挂载卷而非容器根文件系统,否则容器重启或OOMKilled后会丢失;需确保HeapDumpPath指向PV/PVC或hostPath挂载点,并配置正确权限、JVM容器内存感知及ExitOnOutOfMemoryError。

堆转储文件必须写到挂载卷,不能写进容器根文件系统
容器重启或 OOMKilled 后,/tmp、/app、/root 这些路径下的文件全丢——包括 heapdump.hprof。你看到 -XX:HeapDumpPath=/app/logs/heap.hprof 配得再准也没用,除非这个路径背后是 Kubernetes PersistentVolume 或 hostPath 挂载点。
常见错误现象:JVM 报了 OutOfMemoryError,也触发了 -XX:+HeapDumpOnOutOfMemoryError,但日志里只有一行 Heap dump file created,实际查不到 .hprof 文件。
- 检查
HeapDumpPath对应的目录是否在volumeMounts中声明,且对应volume是emptyDir(不行)、hostPath(可临时用)、PersistentVolumeClaim(推荐) - 路径中避免硬编码时间戳,比如
$(date +...)在启动脚本里能跑,但在 YAML 里会被 kubelet 当字符串处理,结果生成的文件名含非法字符或根本没变量替换 - 确保容器内用户(如
1001)对挂载目录有写权限;CNFS/NAS 类存储还要确认 UID/GID 映射和 POSIX 权限是否一致
GC 日志和 hs_err 日志也要落盘,且需轮转
-Xlog:gc*:file=/logs/gc.log 这类配置,如果 /logs 没挂载,日志就写进容器层,随 Pod 删除一起消失。更麻烦的是,不加轮转控制,单个 GC 日志可能撑爆容器磁盘配额(尤其 filesize=10M 不设 filecount 时)。
典型问题:Pod 运行三天后突然无法启动,kubectl describe pod 显示 Failed to create container,其实是 /logs 所在挂载点已满,kubelet 拒绝创建新容器。
- 必须用
-Xlog:gc*:file=/logs/gc-%t.log:filecount=5,filesize=10M,其中%t是 JVM 内置时间格式,安全可靠 -
-XX:ErrorFile=/logs/hs_err_pid%p.log中的%p表示进程 PID,比硬写$$更准确($$是 shell 的当前进程 ID,不是 Java 进程) - 不要依赖容器启动脚本里的
find ... -mtime +2 -delete做日志清理——它可能在 JVM 还没写完日志时就删了正在写的文件,造成日志截断
JVM 必须感知容器内存限制,否则堆大小算错
OpenJDK 8u191+ 和 OpenJDK 11+ 默认启用 -XX:+UseContainerSupport,但很多镜像仍用旧版基础镜像(如 openjdk:8-jre-slim),或者显式关掉了它。结果就是:Pod 限制 2Gi 内存,JVM 却按宿主机 64Gi 算出 16Gi 初始堆,一启动就被 cgroups 杀掉(exit code 137)。
验证方式很简单:kubectl exec -it <pod> -- java -XX:+PrintFlagsFinal -version | grep MaxHeapSize</pod>,看输出的 uintx MaxHeapSize 是否接近你的 memory.limit(比如 2Gi → ~2048M)。
- 强制开启容器感知:
-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0(别用MaxRAM,它已被废弃) - 避免混用
-Xmx和MaxRAMPercentage:后者优先级更高,前者会被忽略,容易造成配置幻觉 - OpenJDK 17+ 推荐用
-XX:InitialRAMPercentage=50.0 -XX:MaxRAMPercentage=75.0,比固定值更适应弹性伸缩场景
OOM 后自动退出比死扛更可控
很多人以为 -XX:+HeapDumpOnOutOfMemoryError 加上就万事大吉,其实不然。JVM 在 OOM 后处于不可靠状态:线程卡死、锁未释放、Netty EventLoop 停摆……此时它还在收请求,响应超时甚至返回脏数据。
更稳妥的做法是让 JVM 主动退出,由 K8s 自动拉起新 Pod,并把故障现场(堆转储、hs_err)留给分析。
- 必须配
-XX:+ExitOnOutOfMemoryError,和HeapDumpOnOutOfMemoryError一起用,顺序无关 - 别信“OOM 后还能继续服务几分钟”的说法——那是压测环境下的侥幸,生产里一次 OOM 往往意味着后续 GC 耗时飙升、HTTP 连接堆积、线程池打满
- K8s readinessProbe 要设置合理:比如
initialDelaySeconds: 30,给 JVM 充足时间完成堆转储再退出,避免 probe 失败导致反复重启干扰诊断
真正难的不是参数怎么写,而是挂载路径是否真实持久、JVM 是否真按容器限制算堆、OOM 后有没有留够时间写完 dump——这三件事漏掉任何一个,日志就白配。


















