Java 笔试中,经常会考 try catch finally执行顺序和返回值的问题,大部分都只在书里面看过,说 finally 一定会执行。其背后的原因值得深究,看看 try catch finally 这个语法糖背后的实现原理
try catch 字节码分析
public class TryCatchFinallyDemo {
public void foo() {
try {
tryItOut1();
} catch (MyException1 e) {
handleException(e);
}
}
}
javac TryCatchFinallyDemo.java; javap -c TryCatchFinallyDemo
在编译后字节码中,每个方法都附带一个异常表(Exception table),异常表里的每一行表示一个异常处理器,由from、to、target、type组成。
其含义是在[from, to]字节码范围内,抛出了异常类型为type的异常,就会跳转到target表示的字节码处。
比如,上面的例子异常表表示:在0到4中间如果抛出了MyException1的异常,就跳转到7执行。
当有多个的catch的情况下,又会是怎样?
public void foo() {
try {
tryItOut2();
} catch (MyException1 e) {
handleException1(e);
} catch (MyException2 e) {
handleException2(e);
}
}
对应字节码如下:
可以看到多一个异常,会在异常表(Exception table里面多一条记录)。
当程序出现异常时,Java 虚拟机会从上至下遍历异常表中所有的条目。当触发异常的字节码索引值在某个异常条目的[from, to]范围内,则会判断抛出的异常与该条目想捕获的异常是否匹配。
如果匹配,Java 虚拟机会将控制流跳转到 target 指向的字节码。
如果遍历完所有的异常表,还未匹配到异常处理器,那么该异常将蔓延到调用方(caller)中重复上述的操作。最坏的情况下虚拟机需要遍历该线程 Java 栈上所有方法的异常表
finally 字节码分析
finally 的字节码分析最为有意思,之前我一直以为 finally 的实现是用 goto 跳转来实现的(低版本的jdk确实如此),实际上并非如此
public void foo() {
try {
tryItOut1();
} catch (MyException1 e) {
handleException(e);
} finally {
handleFinally();
}
}