OOM后如何触发进程重启

1. 问题本质

java.lang.OutOfMemoryError: Garbage-collected heap size exceeded 的含义是:

  1. JVM 已经把堆里绝大部分对象都 GC 掉了
  2. 但可用堆仍然小于 JVM 的“最低可用阈值”
  3. 这是一个 Error,不是 Exception
  4. 默认情况下: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
2
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/heapdump

完整推荐组合:

1
2
3
4
5
-Xms2g
-Xmx2g
-XX:+ExitOnOutOfMemoryError
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/heapdump

3. 外部兜底方案

3.1 Docker / docker-compose 场景

3.1.1 让 JVM 退出 → Docker 自动拉起

1
restart: always

前提
你已经用了 -XX:+ExitOnOutOfMemoryError


3.2 Kubernetes 场景

3.2.1 让 OOM 变成容器级失败

1
2
3
resources:
limits:
memory: 2Gi

如果:

  • JVM 没及时退出
  • 内存继续增长

Linux OOM Killer 直接杀容器

3.2.2 探针触发重启

1
2
3
4
5
6
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10

当:

  • 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
2
3
4
#!/bin/bash
PID=$1
echo "OOM happened, killing $PID" >> /var/log/oom.log
kill -9 $PID

⚠️ 不如 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
2
-XX:+ExitOnOutOfMemoryError
-XX:+HeapDumpOnOutOfMemoryError