Exception 和 Error 是 Throwable 的两个平级子类,非父子关系;catch(Exception e) 无法捕获 OutOfMemoryError 等 Error;Error 表示 JVM 严重故障,不应被捕获恢复,而应快速退出。

Exception 和 Error 是平级兄弟,不是父子关系
你写 catch (Exception e) 永远抓不到 OutOfMemoryError,因为后者根本不在 Exception 的继承链上——它和 Exception 一样,都是 Throwable 的直接子类。这个结构决定了:它们代表的问题类型、处理权限、编译器态度,全都不一样。
Java 虚拟机抛出 Error 时,通常意味着“地基塌了”:栈空间炸穿、堆内存彻底耗尽、类加载器找不到核心类……此时线程可能已损坏,JVM 自身都难保,更别提靠 catch 后继续执行业务逻辑。
该捕就捕,该放就放:不是所有 Throwable 都值得 try-catch
立即学习“Java免费学习笔记(深入)”;
-
Checked Exception(如IOException、SQLException):编译器强制你管——不try-catch,也不throws,代码直接编译失败 -
RuntimeException(如NullPointerException、ArrayIndexOutOfBoundsException):编译器不管,但你应该用校验提前拦住,而不是靠catch救火 -
Error全系:编译器完全放行,但你 不该写catch (Error e)——除非你在写 APM 监控入口,仅用于打日志 +System.exit(1),绝不尝试恢复
别在业务代码里 catch StackOverflowError 或 OutOfMemoryError
看这段看似“兜底”的代码:
public class BadPractice {
public static void main(String[] args) {
try {
recurseForever();
} catch (StackOverflowError e) {
System.out.println("我抓住了!"); // ❌ 危险:此时调用栈已破坏,System.out 可能根本写不出
// 后续任何逻辑(比如记录日志、关闭资源)都不可信
}
}
static void recurseForever() { recurseForever(); }
}
真实场景中,StackOverflowError 发生时,当前线程的栈帧早已溢出,连 finally 块都未必能安全执行;OutOfMemoryError 出现时,JVM 很可能连新对象都分配不出,更别说构造 Logger 实例了。
真正该做的是:查递归深度、加栈大小参数(-Xss)、分析 heap dump、检查依赖冲突或类路径污染——而不是给 Error 加一层 try-catch 掩盖问题。
自定义异常永远别继承 Error
如果你写了一个叫 InvalidConfigError 的类,并让它继承 Error,那它就自动变成“不可恢复的系统级崩溃”,编译器不会提醒你处理,调用方也无从预期——这违背了语义契约。
正确做法始终是:
- 业务异常 → 继承
Exception(需显式处理)或RuntimeException(如IllegalArgumentException风格) - 想表达“配置错得离谱,程序没法启动”?用
IllegalArgumentException或自定义ConfigValidationException,别碰Error
最容易被忽略的一点:很多开发者以为 Error 是“更严重的 Exception”,于是下意识去 catch 它;其实恰恰相反——Error 是 JVM 在说“我不行了”,这时候最克制的反应,就是让程序尽快、干净地退出,而不是假装还能活。










