在Java里异常和错误有什么区别_JavaThrowable结构说明

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

  • Checked Exception(如 IOExceptionSQLException):编译器强制你管——不 try-catch,也不 throws,代码直接编译失败
  • RuntimeException(如 NullPointerExceptionArrayIndexOutOfBoundsException):编译器不管,但你应该用校验提前拦住,而不是靠 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 在说“我不行了”,这时候最克制的反应,就是让程序尽快、干净地退出,而不是假装还能活。