Java异常处理需规范捕获、记录和分层响应:未捕获RuntimeException会导致线程终止,静默吞异常掩盖问题;checked exception应分层处理而非机械throws或转runtime;资源必须用try-with-resources显式关闭;全局异常处理器不等同熔断降级,需结合超时、重试、降级与上下文日志保障稳定性。

异常没捕获或乱吞,系统就容易挂
Java里未捕获的 RuntimeException 会向上抛到线程的 UncaughtExceptionHandler,如果没设,默认打印堆栈后终止线程。Web应用中,一个请求线程崩了可能只是单次失败;但后台定时任务、消息消费者这类长期运行的线程一旦因 NullPointerException 或 ArrayIndexOutOfBoundsException 意外退出,后续消息就积压甚至丢失。
更危险的是“静默吞异常”:写个空的 catch 块,或者只打一句 log.info("出错了") 却不记录异常本身。问题现场消失,监控看不到错误率上升,但业务逻辑实际已跳过关键步骤——比如支付成功后没发MQ、库存没回滚。
- 所有
catch块必须至少记录e.printStackTrace()或用日志框架输出完整e - 对非预期异常(如
IOException出现在本该只抛IllegalArgumentException的校验方法里),不要假设它“偶尔发生”,要查根本原因 - 避免在
finally里抛新异常,否则会覆盖原始异常,导致根因丢失
Checked Exception 强制处理,但滥用反而破坏稳定性
IOException、SQLException 这类 checked exception 本意是让开发者正视可恢复错误,但现实中常被机械式处理:要么层层往上 throws,把本该在 DAO 层重试的数据库超时,一路推到 Controller,最后返回 500;要么在 service 层 catch 后包装成 RuntimeException,等于绕过编译检查,又失去强制处理意义。
真正提升稳定性的做法是分层响应:
立即学习“Java免费学习笔记(深入)”;
- DAO 层:对
SQLException做分类,SQLTimeoutException可重试,SQLSyntaxErrorException直接抛 runtime - Service 层:不暴露底层异常类型,统一转为业务异常(如
InsufficientBalanceException),并附带可操作的错误码 - Controller 层:只处理两类异常——业务异常(返回 4xx + 提示文案)、系统异常(记录告警 + 返回通用 500)
try-with-resources 不只是语法糖,漏写真会拖垮系统
文件句柄、数据库连接、HTTP 客户端连接这些资源,不显式关闭会导致句柄泄漏。JVM 不会因为对象被 GC 就自动释放 native 资源,finalize() 已被弃用,靠它回收几乎等于等死。
常见错误是以为“流关不关影响不大”,但高并发下:FileInputStream 积压导致 java.io.IOException: Too many open files;HttpClient 连接不释放触发连接池耗尽,后续请求卡在 ConnectionPoolTimeoutException。
- 所有实现
AutoCloseable的资源(包括Connection、Statement、ResultSet、CloseableHttpClient)必须用 try-with-resources - 自定义资源类务必实现
AutoCloseable,并在close()中释放所有依赖资源 - 不要在 try-with-resources 的
resource表达式里调用可能抛异常的方法(如new FileInputStream(file)抛FileNotFoundException),否则资源变量为 null,后续无法 close
全局异常处理器和熔断降级不是一回事
Spring 的 @ControllerAdvice 或 Servlet 的 ErrorPage 只负责统一捕获异常、返回友好响应,它不阻止异常发生,也不缓解下游压力。而稳定性核心是“故障隔离”:当订单服务调用用户服务超时时,不能让整个下单链路卡住,得快速失败并走降级逻辑(如用缓存用户数据、默认头像)。
这需要结合具体场景选型:
- 内部 RPC 调用:用 Sentinel 或 Hystrix 配置
fallback和fallbackMethod,超时/异常时执行备用逻辑 - HTTP 外部依赖:用
OkHttpClient的connectTimeout+readTimeout控制单次等待,配合重试拦截器(注意幂等性) - 数据库慢查询:在 MyBatis 的
configuration.defaultStatementTimeout设全局超时,再用 Druid 的stat-sql监控慢 SQL 并优化
异常处理机制本身不会让系统更稳,只有当它和超时控制、重试策略、降级开关、资源隔离一起落地,才真正构成健壮性设计。最容易被忽略的,是异常日志里缺失的关键上下文——比如没打上 traceId,就无法关联链路;没记录入参,就无法复现问题。这些细节,比 catch 块写几行代码重要得多。










