java容器假死是因oom未退出导致gc死循环或线程阻塞,根本原因之一是未配置-xx:+exitonoutofmemoryerror;典型表现为容器状态up但请求无响应、jps可见进程、jstat显示fgc持续失败、日志停更、线程堆栈大量waiting/blocked且含gc相关调用。

线上 Java 容器出现“假死”——进程没退出、CPU 很低、请求无响应,但容器状态仍是 Up,既不崩溃也不重启,这往往是因为 JVM 遇到 OOM 后没退出,卡在 GC 死循环或线程阻塞中。根本原因之一就是没配 -XX:+ExitOnOutOfMemoryError。
看容器是否真在“假死”,而非已退出
先确认现象本质:执行 docker ps 或 kubectl get pod,如果容器状态是 Up X hours(长时间运行),但业务不可用,才属于假死;如果是 Exited (137),那其实是被 OOMKilled 了,属于正常退出,不算假死。
假死的典型特征:
- 容器
STATUS显示Up,但docker logs最后一条日志停在很久以前 -
docker exec -it jps -l能看到 Java 进程,但curl localhost:8080/actuator/health超时或返回DOWN -
docker exec -it jstat -gc <pid></pid>显示 YGC 频繁、FGC 持续失败、OU(老年代使用量)接近OC(老年代容量),且OGCMN/OGCMX不变
查 JVM 是否遭遇 OOM 却未退出
进入容器,检查 JVM 是否已抛出 OOM 但仍在苟延残喘:
- 运行
jcmd <pid> VM.native_memory summary</pid>,看committed是否远超-Xmx(说明元空间/NIO 直接内存等区域溢出,也可能导致假死) - 用
jstack <pid></pid>查看线程栈:大量线程处于WAITING或BLOCKED,且堆栈里反复出现ReferenceHandler、Finalizer或 GC 相关调用,大概率是 GC 压力过大无法回收 - 检查应用日志末尾是否有
java.lang.OutOfMemoryError,但没有伴随Process finished with exit code或 JVM 崩溃字样——这就是 OOM 发生了,JVM 却没终止进程
验证 -XX:+ExitOnOutOfMemoryError 是否缺失
这是最直接的判断依据:
- 执行
docker inspect | grep 'java.*-Xmx',查看启动命令中是否含-XX:+ExitOnOutOfMemoryError - 若只看到
-XX:+HeapDumpOnOutOfMemoryError或完全没这两个参数,基本可断定问题根源在此 - 注意:即使配置了该参数,也要确认它写在
java命令开头,而不是被其他 shell 封装逻辑覆盖或拼接错误
临时补救与长期修复
假死发生时,不能等自动恢复,需人工干预:
- 立即执行
docker kill --signal=SIGQUIT触发线程 dump,再docker kill强制终止,让容器按 restart 策略重启 - 上线前必须在启动脚本或 Kubernetes YAML 的
args中加入:-XX:+ExitOnOutOfMemoryError -XX:MaxRAMPercentage=75.0(配合 cgroup 内存限制更稳妥) - K8s 环境务必同步配置
readinessProbe和livenessProbe,避免假死实例继续被路由流量










