捕获 throwable 会吞掉 outofmemoryerror、stackoverflowerror 等致命 error,导致服务假死、内存泄漏、线程卡死;应避免 catch (throwable),优先分层捕获具体异常类型。

捕获 Throwable 会吞掉哪些不该吞的异常
Java 中 Throwable 是所有异常和错误的父类,包括 Exception(可捕获、可恢复)和 Error(通常不可恢复,如 OutOfMemoryError、StackOverflowError)。用 catch (Throwable t) 看似“兜底”,实则把本该让 JVM 崩溃或容器重启的致命错误也一并吞了。
常见错误现象:服务还在跑,但内存持续上涨不释放;线程卡死无响应;日志里只有一句“caught throwable”,没堆栈;GC 频繁但 OutOfMemoryError 被静默吃掉。
-
Error类型本就不该被应用层捕获处理,JVM 设计上就期望它传播出去触发终止或监控告警 - 某些
Error(如NoClassDefFoundError)可能由类加载失败引起,捕获后继续执行大概率导致后续NullPointerException或行为错乱 - 像
ThreadDeath这种被stop()方法抛出的特殊Error,捕获后线程实际未终止,资源泄漏风险极高
catch (Exception) 和 catch (Throwable) 的典型误用场景
很多老代码或框架封装层为了“防止程序崩溃”,在顶层拦截器、过滤器、线程池 UncaughtExceptionHandler 里写 catch (Throwable)。这在 Web 应用中尤其危险——HTTP 请求看似返回了 200,但内部已发生 LinkageError,后续同一线程处理其他请求时直接报 NoSuchMethodError。
使用场景对比:
立即学习“Java免费学习笔记(深入)”;
- 需要统一记录日志 + 返回友好提示?→ 用
catch (Exception),明确排除Error - 想做“最后防线”防止线程退出?→ 用
Thread.setDefaultUncaughtExceptionHandler,而不是在业务逻辑里catch (Throwable) - 测试环境模拟崩溃?→ 可以主动 throw
new OutOfMemoryError()验证是否真被吞掉
为什么 catch (Exception) 也不总是安全
不是说换成 catch (Exception) 就万事大吉。比如 RuntimeException 子类(如 NullPointerException、IllegalArgumentException)也是 Exception 的子类,但它们往往反映的是编程错误,而非外部异常条件。
参数差异和影响:
-
catch (Exception)仍会捕获RuntimeException,掩盖空指针、数组越界等本该修复的 bug - 更合理的做法是分层捕获:业务异常(自定义
BusinessException)用具体类型;运行时异常一般不捕获,靠单元测试和静态检查提前暴露 - 若必须兜底,建议只捕获
Exception的非运行时子类:catch (IOException | SQLException | InterruptedException e)
一个能落地的异常捕获策略
不要追求“一次 catch 全局兜底”。真实项目里,异常处理应该按职责切分:
- Controller 层:只捕获明确的业务异常(如
OrderNotFoundException),转成 HTTP 状态码;不碰Exception或Throwable - Service 层:抛出受检异常(
throws IOException)或自定义异常,不自己try-catch - 最外层(如 Spring 的
@ControllerAdvice):用@ExceptionHandler(Exception.class),但显式排除Error.class子类(Spring 默认已做到) - 线程池任务:用
Future.get()或包装Runnable捕获异常,避免catch (Throwable)在run()内部
真正难的不是语法怎么写,而是每次写 catch 前得问一句:这个异常我打算怎么恢复?如果答案是“打日志然后继续”,那大概率该删掉这行 catch。








