public class Test {
public void foo() {
try {
tryItOut();
} catch (MyException e) {
handleException(e);
}
}
private void tryItOut() throws MyException {
}
class MyException extends Exception {
}
private void handleException(MyException e) {
}
}
javap 查看字节码
javap -c -v Test
复制 foo() 函数部分,如下
public void foo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: aload_0
1: invokespecial #15 // Method tryItOut:()V
4: goto 13
7: astore_1
8: aload_0
9: aload_1
10: invokespecial #18 // Method handleException:(LTest$MyException;)V
13: return
Exception table:
from to target type
0 4 7 Class Test$MyException
14 ~ 16 行:在生成的字节码中,每个方法都会附带一个异常表(Exception table),其中每一行表示一个异常处理器,由 from 指针、to 指针、target 指针、所捕获的异常类型 type 组成
这些指针的值是字节码索引,用于定位字节码,其含义是在 [from, to) 字节码范围内,抛出异常类型为 type 的异常,就会跳转到 target 表示的字节码处
上面的栗子,异常表表示:在 0 到 4 中间(不含 4)如果抛出了 MyException 的异常,就跳转到 7 执行
修改上面的栗子,多加几个 catch 块
public class Test {
public void foo() {
try {
tryItOut();
} catch (MyException e) {
handleException(e);
} catch (MyException1 e) {
handleException1(e);
}
}
private void tryItOut() throws MyException, MyException1 {
}
class MyException extends Exception {
}
private void handleException(MyException e) {
}
class MyException1 extends Exception {
}
private void handleException1(MyException1 e) {
}
}
javap 查看字节码
javap -c -v Test
复制 foo() 函数部分,如下
public void foo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=1
0: aload_0
1: invokespecial #15 // Method tryItOut:()V
4: goto 22
7: astore_1
8: aload_0
9: aload_1
10: invokespecial #18 // Method handleException:(LTest$MyException;)V
13: goto 22
16: astore_1
17: aload_0
18: aload_1
19: invokespecial #22 // Method handleException1:(LTest$MyException1;)V
22: return
Exception table:
from to target type
0 4 7 Class Test$MyException
0 4 16 Class Test$MyException1
很明显可以看出,异常表多了一行记录
Java 虚拟机会从上到下遍历异常表中所有的条目。当触发异常表的字节码索引值在某个异常条目范围内,则会判断抛出的异常与该条目想捕获的异常是否匹配,当前栗子即为在 0 到 4 中间(不含 4)可能会抛出 MyException 或 MyException1 异常,具体抛哪个,虚拟机会去匹配实际抛出的异常与该条目想捕获的异常是否匹配
public class Test {
public void foo() {
try {
tryItOut();
} catch (MyException e) {
handleException(e);
} finally {
handleFinally();
}
}
private void tryItOut() throws MyException {
}
class MyException extends Exception {
}
private void handleException(MyException e) {
}
private void handleFinally() {
}
}
javap 查看字节码
javap -c -v Test
复制 foo() 函数部分,如下
public void foo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: invokespecial #15 // Method tryItOut:()V
4: goto 27
7: astore_1
8: aload_0
9: aload_1
10: invokespecial #18 // Method handleException:(LTest$MyException;)V
13: aload_0
14: invokespecial #22 // Method handleFinally:()V
17: goto 31
20: astore_2
21: aload_0
22: invokespecial #22 // Method handleFinally:()V
25: aload_2
26: athrow
27: aload_0
28: invokespecial #22 // Method handleFinally:()V
31: return
Exception table:
from to target type
0 4 7 Class Test$MyException
0 13 20 any
24 ~ 27 行:表示在 0 到 4 中间(不含 4)如果抛出了 MyException 的异常,就跳转到 7 执行;在 0 到 13 中间(不含 13)如果抛出了 any 类型的异常(也就是未捕获的异常),就跳转到 20 执行
上面的字节码其实对应代码的几种可能的执行流程
情况 ①:try 代码块正常执行,没有异常,对应上面的字节码第 6、7、8、21、22、23 行
// 当前情况下,22 行对应 finally 代码块的执行
情况 ②:try 代码块执行过程中遇到异常,且该异常在异常表范围内有匹配的异常(也就是说有对应的 catch 块捕获了出现的异常),对应上面的字节码第 6、7、9、10、11、12、13、14、15、23 行
// 当前情况下,14 行对应 finally 代码块的执行
情况 ③:try 代码块执行过程中遇到异常,且该异常在异常表范围内有匹配的异常,但是 catch 块中的代码执行过程中出现了异常,对应上面的字节码第 6、7、9、10、11、12、13、16、17、18、19、20 行
// 当前情况下,18 行对应 finally 代码块的执行,注意 throw 指令执行是在 20 行,也就是说如果在 catch 代码块中即使出现异常,也会先执行 finally 代码块,然后再抛 catch 块中出现的异常
综上所述,不管什么情况下,finally 代码块都会得到执行(●°u°●) 」
public static int foo() {
int x = 0;
try {
return x;
} finally {
++x;
}
}
public static void main(String[] args) {
int res = foo();
System.out.println(res);
}
返回 0