OOM后如何触发进程重启
1. 问题本质
java.lang.OutOfMemoryError: Garbage-collected heap size exceeded 的含义是:
- JVM 已经把堆里绝大部分对象都 GC 掉了
- 但可用堆仍然小于 JVM 的“最低可用阈值”
- 这是一个
Error,不是Exception - 默认情况下:JVM 可能继续运行(半死不活)
也就是说:
- OOM ≠ JVM 进程退出
- 容器 / systemd / k8s 感知不到
- 业务线程可能卡死,但 PID 还在
所以必须人为制造“不可恢复失败”。
2. 最直接推荐的方式
2.1 JVM 参数:OOM 直接杀进程
1 | -XX:+ExitOnOutOfMemoryError |
效果:
- 一旦发生任何
OutOfMemoryError - JVM 立刻
System.exit(1) - 进程退出 → 外部拉起新进程
这是 JDK 8u92+ / JDK 11+ 的标准做法。
2.2 配合 OOM Dump
1 | -XX:+HeapDumpOnOutOfMemoryError |
完整推荐组合:
1 | -Xms2g |
3. 外部兜底方案
3.1 Docker / docker-compose 场景
3.1.1 让 JVM 退出 → Docker 自动拉起
1 | restart: always |
前提:
你已经用了 -XX:+ExitOnOutOfMemoryError
3.2 Kubernetes 场景
3.2.1 让 OOM 变成容器级失败
1 | resources: |
如果:
- JVM 没及时退出
- 内存继续增长
➡ Linux OOM Killer 直接杀容器
3.2.2 探针触发重启
1 | livenessProbe: |
当:
- OOM 后线程池耗尽
- health endpoint 无法响应
- kubelet 重启 Pod
4. 自定义可控触发重启
4.1 使用 -XX:OnOutOfMemoryError
1 | -XX:OnOutOfMemoryError="kill -9 %p" |
或者:
1 | -XX:OnOutOfMemoryError="sh /opt/scripts/oom.sh %p" |
oom.sh 示例:
1 |
|
⚠️ 不如 ExitOnOutOfMemoryError 干净,但在老 JVM 可用
5. OOM触发进程重启生产级整体架构
flowchart LR
A[Java进程] -->|OOM| B[JVM Exit]
B --> C[进程退出]
C --> D[Docker / K8s]
D --> E[自动重启]
A -->|Dump| F[HeapDump]
6. 结论
OOM 不可恢复,必须 Fail Fast。
JVM 层负责“立刻死”,平台层负责“立刻拉”。
最低成本、最高收益配置:
1 | -XX:+ExitOnOutOfMemoryError |