Java 实战视角:Error 和 Exception 到底有什么区别?
这是 Java 面试和实际开发中最常被问到的问题之一,但很多人在实际 coding 时还是会混淆。下面从定义 → 分类 → 是否可恢复 → 处理策略 → 真实场景完整对比,并给出最实用的判断标准。
核心一句话总结(面试/工作最常用)
- Exception:程序自己或外部环境造成的可预料、可恢复的异常情况,应该被捕获和处理。
- Error:JVM/操作系统/硬件层面的严重错误,正常应用不应该尝试捕获,通常也无法恢复,程序基本要挂。
详细对比表(最清晰版)
| 对比维度 | Exception | Error |
|---|---|---|
| 父类 | java.lang.Exception | java.lang.Error |
| 共同父类 | Throwable | Throwable |
| 是否 checked | 分 checked 和 unchecked(RuntimeException 子类) | 全是 unchecked |
| 发生时机 | 主要运行时(少量编译时,如 checked) | 运行时(极少编译时) |
| 是否可恢复 | 大多数可以恢复(合理处理后继续运行) | 几乎不可恢复(恢复基本无意义) |
| 合理做法 | 应该捕获、处理或抛出给上层 | 不应该捕获(除非极特殊场景,如监控) |
| 典型代表 | NullPointerException, IOException, SQLException, IllegalArgumentException | OutOfMemoryError, StackOverflowError, VirtualMachineError |
| 谁的责任 | 程序员 / 业务逻辑 / 输入参数 / 外部资源 | JVM / 操作系统 / 硬件 / 环境 |
| 出现频率(生产环境) | 非常高(每天都可能) | 较低,但一旦出现基本是致命的 |
真实项目中最常见的 Error 子类(见过就认得出)
java.lang.OutOfMemoryError
├─ Java heap space 最常见:堆内存不足
├─ GC overhead limit exceeded GC 太频繁,回收效果差
├─ PermGen space / Metaspace(Java8+) 元空间/永久代溢出
└─ Native memory 本地内存耗尽
java.lang.StackOverflowError 递归太深或线程栈太小
java.lang.VirtualMachineError 虚拟机内部错误(极少见)
真实项目中最常见的 Exception 子类分类
Checked Exception(必须处理,否则编译不通过)
- IOException / FileNotFoundException
- SQLException
- ClassNotFoundException
Unchecked Exception(RuntimeException 及其子类,运行时才暴露)
- NullPointerException
- ArrayIndexOutOfBoundsException
- IllegalArgumentException
- ClassCastException
- NumberFormatException
- ArithmeticException(/0)
- ConcurrentModificationException
实战代码对比(一看就懂)
// 应该捕获的 Exception(正常业务场景)
public String readConfig(String path) {
try {
return Files.readString(Paths.get(path));
} catch (IOException e) { // ← 合理捕获
log.error("读取配置文件失败,使用默认配置", e);
return DEFAULT_CONFIG;
}
}
// 不应该捕获的 Error(几乎没人这么写)
public void processBigData() {
try {
// 假设这里分配超大内存
byte[] huge = new byte[Integer.MAX_VALUE];
} catch (OutOfMemoryError e) { // ← 极不推荐
// 这里基本救不回来,还可能让问题更复杂
log.fatal("内存耗尽", e);
System.exit(1); // 直接退出更合理
}
}
真实项目推荐做法:
// 推荐:只捕获 Exception,不捕获 Error
public void someServiceMethod() {
try {
// 业务代码
} catch (Exception e) { // 通常捕获 Exception
if (e instanceof RuntimeException) {
// 可能是 bug,记录详细日志
log.error("业务运行时异常", e);
throw e; // 或包装后抛出
} else {
// checked exception,尝试恢复或友好提示
log.warn("可预期的异常", e);
// 返回降级结果
}
}
// 注意:这里不会写 catch(Error e) 除非你真的在做 JVM 监控工具
}
面试/工作中最常被问的几个追问
- 能不能 catch Error?
→ 语法上可以,但强烈不推荐。捕获后也基本救不回来,还可能掩盖真正问题。 - OutOfMemoryError 能不能恢复?
→ 极个别场景可以(比如调大堆、降级功能),但绝大多数情况只能重启或缩容。 - 为什么 RuntimeException 也叫 unchecked exception?
→ 因为它和 Error 一样,不需要在方法上声明 throws,属于“程序员的 bug”居多。 - 生产环境遇到 OutOfMemoryError 怎么办?
→ 先看是哪种 OOM(heap / metaspace / native)
→ 收集 dump(jmap / jcmd / arthas)
→ 分析原因(内存泄漏?大对象?GC 策略?)
→ 临时加大堆 / 临时降级 / 重启
→ 长期修复代码或架构
一句话记住:
“Exception 是我的锅,我来修;Error 是 JVM/机器的锅,我尽力跑路”
有哪种具体场景(比如 OOM 排查、自定义异常、Spring 中的异常处理)还想再深入聊聊?