简单描述
java中的try-catch通过异常表和栈展开来实现
异常表(exception-table)
每个方法的字节码中都有一个异常表,用于记录try-catch块的作用范围和对应的异常处理逻辑
异常表的每个条目包含以下信息:
起点,终点,处理代码的位置,捕获异常的类型
起点(start_pc):try块的起始指令偏移量
终点(end_pc):try块的结束指令偏移量(不包含该指令)
处理代码位置(handler_pc):catch块的第一条指令偏移量
捕获的异常类型(catch_type):要捕获的异常类(如java/lang/Exception),若为0表示捕获所有异常(finally块)
字节码结构
Exception table:
start end handler type
0 10 13 java/io/IOException
0 10 20 java/lang/Exception
异常处理流程
抛出异常,从异常表判断异常是否在处理逻辑内(也就是是否被try-catch{}包围),
代码中抛出异常时,JVM会执行以下步骤:
创建异常对象:实例化抛出的异常(如new IOException()
)
查找异常表:从当前方法的异常表中,按顺序匹配以下条件:
异常抛出的位置是否在某个条目的[start_pc, end_pc)
范围内。
抛出的异常是否是catch_type
的子类(或自身)
跳转到处理代码:
若找到匹配条目,跳转到handler_pc
执行catch
块
若未找到,触发栈展开:弹出当前栈帧,回到调用者方法重复上述过程。栈展开确保异常沿调用链向上传播,直到被处理或终止线程
未捕获异常:若所有栈帧均未处理异常,线程终止并打印堆栈跟踪
finally块的实现
finally 块的核心是:无论 try 或 catch 块中是否抛出异常或提前返回,finally 中的代码必须执行
为了实现这一点,JVM 的编译器(如 javac)在生成字节码时,会通过两种机制来确保 finally 的执行
finally块通过两种方式实现:
代码复制:编译器将finally
代码复制到try
和catch
块的所有退出路径(包括return
或异常抛出之后)。
异常表条目兜底:若finally
需要处理异常退出,会生成一个catch_type=0
的条目,捕获所有异常并执行finally
代码,之后重新抛出异常
代码复制
编译器会将 finally 块中的代码复制到所有可能的退出路径,包括:
try 块正常结束后的退出路径。
catch 块处理完异常后的退出路径。
try 或 catch 块中的 return、break、continue 语句之前
java代码
public void example() {
try {
System.out.println("try");
} catch (Exception e) {
System.out.println("catch");
} finally {
System.out.println("finally");
}
}
编译后的字节码逻辑
// try 块
L0:
System.out.println("try");
// 复制 finally 代码到 try 块末尾
System.out.println("finally");
return;
// catch 块
L1:
System.out.println("catch");
// 复制 finally 代码到 catch 块末尾
System.out.println("finally");
return;
// 异常表条目(自动处理异常后的 finally)
Exception table:
start=L0, end=L0, handler=L1, type=Exception
关键点:
finally 的代码会被复制到 try 和 catch 的末尾,确保正常流程下一定会执行
如果 try 或 catch 中有 return,编译器会先执行 finally 代码,再执行 return
异常表兜底(处理未捕获的异常)
如果 try 或 catch 块中抛出了未被捕获的异常,或者有 throw 语句,JVM 会通过异常表跳转到 finally 代码,执行后再重新抛出异常。
异常表条目
编译器会生成一个特殊的异常表条目,用于捕获所有类型的异常(catch_type=0)
确保任何未处理的异常都会先执行 finally,再继续传播异常
java代码
public void example() {
try {
throw new IOException();
} finally {
System.out.println("finally");
}
}
字节码的异常表会生成如下头目
Exception table:
start=L0, end=L1, handler=L2, type=0 // type=0 表示捕获所有异常
对应的执行流程:
try
块抛出IOException
。- JVM 查找异常表,发现
type=0
的条目(匹配所有异常)。 - 跳转到
handler=L2
(finally
代码的位置)执行System.out.println("finally")
。 - 重新抛出异常,继续栈展开
假设代码中有 try 和 finally,但没有 catch:
try {
throw new Exception();
} finally {
System.out.println("finally");
}
执行步骤:
try
块抛出异常,JVM 创建异常对象。- 直接查找当前方法的异常表,找到
catch_type=0
的条目,跳转到finally
代码。 - 执行
finally
块中的代码。 - 重新抛出异常,由外层调用者处理