java异常自愈是基于监控指标、业务规则与执行动作构建的分级响应闭环,而非自动重启;需按故障层级(如单实例gc卡死、全链路5xx飙升等)采取差异化处置,并依赖外部进程管理器或k8s实现可靠重启,同时通过多维gc指标组合判断问题根因,且必须配备熔断开关防告警风暴。

Java异常自愈不是“自动重启程序”,而是对故障根因的分级响应
Java里没有内置的“异常自愈系统”——它不是JVM或Spring开个开关就能启用的功能,而是你用监控指标+业务规则+执行动作拼出来的闭环。关键在于:**不能一出错就杀进程重启**,否则可能把正在处理支付回调的线程干掉,导致订单状态不一致。
真正有效的自愈,是先判断“错在哪一层”:是单实例GC卡死?还是服务整体超时率飙升?是配置写错触发了IllegalArgumentException?还是下游DB连不上引发雪崩?不同层级的异常,对应的动作完全不同。
- 单实例堆内存持续告警 → 触发
jmap -dump+ 优雅下线 + 流量切走 - 全链路5xx错误率突增 → 调用熔断API(如
DubboAdaptiveFaultTolerance)临时降级非核心接口 - 配置中心推送非法值导致启动失败 → 回滚到上一版配置,而非反复重启崩溃
WatchService监听失败后自动重启,为什么裸写while(true)会失效?
很多人用WatchService监听文件变更后加个while (true)循环,结果一旦watchService.take()抛ClosedWatchServiceException,整个线程就静默退出,没人知道它挂了——因为没做异常兜底,也没绑定Spring生命周期。
正确做法是把监听逻辑扔进一个托管线程池,并在catch块里加退避重试:
立即学习“Java免费学习笔记(深入)”;
catch (ClosedWatchServiceException e) {
log.error("WatchService closed unexpectedly", e);
break; // 不再重试,交由上层ExecutorService决定是否重建
} catch (Exception e) {
log.error("Unexpected error in watcher, retrying in 5s", e);
try { Thread.sleep(5_000); }
catch (InterruptedException ex) { Thread.currentThread().interrupt(); break; }
}
注意:Thread.sleep()不能放在InterruptedException外层统一捕获——那会掩盖真正的中断意图;也别用Executors.newCachedThreadPool(),它不支持优雅关闭,Spring停机时线程可能被强杀。
用Runtime.getRuntime().addShutdownHook()实现自重启,最大的坑是“分身还没活过来,本体就死了”
网上流传的“shutdown hook里起新进程然后System.exit(0)”方案,在真实环境大概率失败:Linux下nohup java -jar xxx.jar &启动后,父进程退出太快,子进程可能被init接管失败,或者日志输出混乱,甚至根本没起来。
更可靠的方式是交由外部进程管理器控制,比如:
- Linux用
systemd配Restart=on-failure,配合RestartSec=10防抖 - 容器化场景直接依赖K8s的
livenessProbe+restartPolicy: Always - 若必须代码内控,至少确保新进程启动后,通过文件锁或HTTP健康检查确认其已ready,再退出旧进程
另外,addShutdownHook只在JVM正常关闭(如kill -15)时触发,遇到kill -9、OOM Killer、主机宕机等场景完全无效——别把它当万能保险。
GC异常触发扩容 or 重启?要看指标组合,不是看单次Full GC次数
只监控GarbageCollectorMXBean.getCollectionCount()并设阈值为“1分钟5次Full GC”,会误伤G1GC在大堆下的正常混合回收(Mixed GC),也可能漏掉ZGC里pause time > 10ms但次数极少的亚秒级卡顿。
真正有用的判定必须是多维交叉:
- 老年代使用率 > 85% 且 持续3分钟 → 可能内存泄漏,优先dump分析,不急着重启
- 单次
collectionTime> 1000ms 且 近5分钟发生3次以上 → 触发实例隔离 + 自动扩容新节点 -
getCollectionCount()突增但collectionTime平稳 → 很可能是年轻代分配速率暴增,应查业务流量尖峰,而非调JVM参数
所有这些动作,都得通过Micrometer推指标到Prometheus,再用Alertmanager配for持续时间条件,而不是在Java里硬编码sleep轮询——后者既不准又占资源。
自愈系统最常被忽略的一点:它必须有“熔断开关”。当告警风暴来袭时,要能自动暂停扩容/重启动作,防止把集群越救越崩。









