基于异常处理机制 相关知识点

一、JVM 层面的异常实现(字节码与异常表)

  1. 异常表(Exception Table)

每个方法的字节码中都有一张异常表,结构如下:

```

起始PC | 结束PC | 处理PC | catch类型(类全限定名)

```

· try 块对应 [起始PC, 结束PC) 的指令范围

· 当该范围内抛出异常,JVM 匹配异常表:

· 若 catch 类型匹配,则跳转到 处理PC

· 若没有匹配项,则方法栈弹出,继续到调用者方法中查找

  1. finally 的字节码实现

· 编译器为 finally 块生成冗余代码:在 try 的正常出口、每个 catch 的出口,以及异常抛出的路径前,都会插入 finally 块的字节码拷贝。

· 因此如果 finally 中有 return,它会覆盖 try/catch 的返回或异常。

  1. athrow 指令

· 抛出异常时,JVM 执行 athrow 指令,将当前栈顶的异常引用抛出,并开始查找异常表。

二、try-with-resources 彻底解密

  1. 语法糖展开前 vs 展开后

原代码:

```java

try (FileInputStream fis = new FileInputStream("a.txt")) {

fis.read();

}

```

编译后等价于:

```java

FileInputStream fis = new FileInputStream("a.txt");

Throwable primaryExc = null;

try {

fis.read();

} catch (Throwable t) {

primaryExc = t;

throw t;

} finally {

if (fis != null) {

if (primaryExc != null) {

try {

fis.close();

} catch (Throwable suppressed) {

primaryExc.addSuppressed(suppressed);

}

} else {

fis.close();

}

}

}

```

· 自动管理多个资源(按声明顺序逆序关闭)

· 抑制异常:primaryExc 为主异常,close 中的异常被添加到抑制链

  1. 自定义自动关闭资源

实现 AutoCloseable 接口即可,close() 方法可以被调用多次(但幂等性由开发者保证)。

三、异常链与堆栈信息高级操作

  1. 保留原始异常的三种方式

```java

// 方式1:构造函数传 cause

throw new MyException("msg", originalException);

// 方式2:initCause

MyException e = new MyException("msg");

e.initCause(originalException);

// 方式3:throwable 的 fillInStackTrace 可覆盖堆栈

e.setStackTrace(new StackTraceElement[0]); // 抹去堆栈(不推荐)

```

  1. 堆栈轨迹的获取与过滤

· Thread.currentThread().getStackTrace() 可获取当前线程的调用栈(不包含异常)

· Throwable.getStackTrace() 返回异常填充时的快照

· Java 9+ 提供了 StackWalker 进行高效的栈遍历和过滤

```java

StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)

.walk(frames -> frames.limit(5).collect(Collectors.toList()));

```

四、多线程与异步异常处理深度解析

  1. 不同提交方式的异常传播

方法 异常去向

Thread.start() 未捕获 线程消亡,异常传递给 Thread.UncaughtExceptionHandler(若设置)

Executor.execute(Runnable) 异常交给 Thread 的 UncaughtExceptionHandler

Executor.submit(Runnable) 异常被包装到返回的 Future<?>,future.get() 抛出 ExecutionException

Executor.submit(Callable) 同上,且 Callable 可以声明受检异常

  1. 全局未捕获异常处理器

```java

Thread.setDefaultUncaughtExceptionHandler((thread, throwable) ->

logger.error("Thread {} failed", thread.getName(), throwable)

);

```

  1. 线程池内部异常处理最佳实践

```java

ThreadPoolExecutor pool = new ThreadPoolExecutor(1,1,0,TimeUnit.SECONDS, new LinkedBlockingQueue<>());

pool.setThreadFactory(r -> {

Thread t = new Thread(r);

t.setUncaughtExceptionHandler((thread, ex) -> {

// 记录异常,甚至重新抛出一个运行时异常

});

return t;

});

```

五、性能分析与调优(精确数据)

  1. 异常对象创建成本

· 填充堆栈轨迹是主要开销:遍历当前栈帧,获取类名、方法名、行号

· 一个异常对象耗时约 1~15 微秒(取决于深度),比普通 if 判断(纳秒级)高几个数量级

· 高频调用中(如每秒百万次),异常可导致吞吐量下降 50%+

  1. 减少异常开销的手段

· 使用 Throwable(String, Throwable, boolean, boolean) 构造器控制是否填充堆栈(Java 7+)

· 参数 writableStackTrace=false 生成无堆栈异常,性能接近普通对象

· 适用场景:已知异常类型但不需要堆栈信息(例如快速失败重试)

· 复用异常对象(不推荐,因为堆栈无法正确反映调用点,且并发时线程不安全)

```java

// 无堆栈异常示例

public class FastException extends Exception {

public FastException() {

super("msg", null, false, false);

}

}

```

  1. 实测对比(粗略参考)

操作 耗时(ns)

if 判断 1~2

new Object() 10~20

new Exception() 1000~5000

new Exception(true) 100~200

六、框架及架构层面专项设计

  1. 统一异常处理(Spring Boot)

```java

@RestControllerAdvice

public class GlobalExceptionHandler {

@ExceptionHandler(IllegalArgumentException.class)

@ResponseStatus(HttpStatus.BAD_REQUEST)

public ErrorResponse handle(IllegalArgumentException e) {

return new ErrorResponse(e.getMessage());

}

}

```

· 注意多个 @ExceptionHandler 的顺序:子类优先于父类

· 可以使用 @ControllerAdvice(assignableTypes = {...}) 限定范围

  1. 分布式系统中的异常封装

· 网络调用异常应明确区分可重试(服务超时、连接重置)和不可重试(业务参数错误)

· 返回前端的错误码与异常类型应隔离:不要将 SQLSyntaxErrorException 直接暴露给用户

```java

// 典型模式

try {

remoteCall();

} catch (TimeoutException e) {

throw new RetryableException("remote timeout");

} catch (RemoteException e) {

throw new BizException("remote error", e);

}

```

  1. 异步回调中的异常处理(CompletableFuture)

· CompletableFuture 提供了 exceptionally、handle 等方法:

```java

CompletableFuture.supplyAsync(() -> {

if (error) throw new RuntimeException("oops");

return "ok";

}).exceptionally(ex -> "fallback")

.thenAccept(System.out::println);

```

· 不处理异常会导致 future.join() / get() 抛出 CompletionException

七、复杂场景陷阱与源码验证

  1. finally 块与 return 的交互详细测试

情况 结果

try { return 1; } finally { return 2; } 返回 2,try 中的返回值被抛弃

try { throw new Ex(); } finally { return; } 异常被吞掉,方法正常返回

try { return obj; } finally { obj = null; } 返回原 obj 的引用,obj 重新赋值不影响返回值(前提是引用变量)

  1. 捕获 Error 是否合理

· 理论上 Error 表示 JVM 严重问题(如 OutOfMemoryError),通常不应捕获

· 但在资源清理代码中,有时需要捕获 Throwable 确保 finally 执行(如框架底层)

  1. 为什么重写 finalize() 时建议捕获 Throwable?

· finalize() 中任何异常会被忽略,对象被标记为已终结;因此建议捕获所有异常并进行日志。

八、专项自测题(带答案)

  1. try-with-resources 中,如果资源构造器抛异常,资源会关闭吗?

答:不会。资源变量为 null,所以不会调用 close。

  1. 一个方法同时声明 throws IOException, SQLException,catch 块可以写 catch (Exception e) 吗?

答:可以,但不推荐,会捕获所有异常,包括未声明的 RuntimeException。

  1. 什么是"抑制异常"?请给出一个典型场景

答:try-with-resources 中,主异常抛出后,close() 又抛出异常,后者被附加为主异常的抑制异常。

4.Thread.setDefaultUncaughtExceptionHandler 对 execute 和 submit 都生效吗?

答:仅对 execute 且未设置线程单独 Handler 时生效;submit 返回的 Future 吞掉了异常(转为返回值)。

相关推荐
沐知全栈开发1 小时前
WebPages 对象
开发语言
likerhood1 小时前
java设计模式 · 适配器模式 (Adapter Pattern)
java·设计模式·适配器模式
谙弆悕博士1 小时前
Lua学习笔记
c语言·开发语言·笔记·学习·lua·创业创新·业界资讯
Data_Journal1 小时前
2026年十大数据集网站
大数据·开发语言·数据库·人工智能·python
cui_ruicheng1 小时前
Linux线程(三):线程同步、互斥与生产者消费者模型
linux·服务器·开发语言
snakeshe10101 小时前
SpringBoot 多人协作平台实战(6):SpringBoot Controller 入门与登录模块开发
java
用户298698530141 小时前
用 Java 操作 Word 文档?试试添加内容控件
java·后端
带刺的坐椅1 小时前
Java AI 框架三国杀:Solon AI vs Spring AI vs LangChain4j 深度对比
java·ai·langchain4j·spring-ai·solon-ai