JVM 字节码(三)异常在字节码中的处理(catch 和 throws)
在 ClassFile 中到底是如何处理异常的呢?
一、代码块异常 catch
catch 中的异常代码块在异常是如何处理的呢?还记得在 Code 属性中有如下的结构吗?这代码的是一个异常表信息。
Code_attribute {
...
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
...
}
案例分析:
public void test() {
try {
FileInputStream in = new FileInputStream("text.txt");
ServerSocket serverSocket = new ServerSocket(9999);
serverSocket.accept();
} catch (FileNotFoundException e) {
} catch (IOException e) {
} catch (Exception e) {
} finally {
System.out.println("finally!");
}
}
这段代码编译后用 jclasslib 查看,字节码和异常表分别如下:
0 new #2 <java/io/FileInputStream>
3 dup
4 ldc #3 <text.txt>
6 invokespecial #4 <java/io/FileInputStream.<init>>
9 astore_1
10 new #5 <java/net/ServerSocket>
13 dup
14 sipush 9999
17 invokespecial #6 <java/net/ServerSocket.<init>>
20 astore_2
21 aload_2
22 invokevirtual #7 <java/net/ServerSocket.accept>
25 pop
26 getstatic #8 <java/lang/System.out>
29 ldc #9 <finally!>
31 invokevirtual #10 <java/io/PrintStream.println>
34 goto 84 (+50)
37 astore_1
38 getstatic #8 <java/lang/System.out>
41 ldc #9 <finally!>
43 invokevirtual #10 <java/io/PrintStream.println>
46 goto 84 (+38)
49 astore_1
50 getstatic #8 <java/lang/System.out>
53 ldc #9 <finally!>
55 invokevirtual #10 <java/io/PrintStream.println>
58 goto 84 (+26)
61 astore_1
62 getstatic #8 <java/lang/System.out>
65 ldc #9 <finally!>
67 invokevirtual #10 <java/io/PrintStream.println>
70 goto 84 (+14)
73 astore_3
74 getstatic #8 <java/lang/System.out>
77 ldc #9 <finally!>
79 invokevirtual #10 <java/io/PrintStream.println>
82 aload_3
83 athrow
84 return
异常表如下:
解读一下异常表, start_pc ~ end_pc 出现的 catch_type 异常由 handler_pc 行的代码进行处理。
- 第一行表示 0~26 行出现的 FileNotFoundException 异常直接跳转到 37(astore_1) 行
- 第二行表示 0~26 行出现的 IOException 异常直接跳转到 49(astore_1) 行
- 第三行表示 0~26 行出现的 Exception 异常直接跳转到 61(astore_1) 行
- any 表示其余的所有异常,字节码层面定义,和 Java 中定义的 Exception 还不太一样
总结:Java 字节码对于异常的处理方式
- 统一采用异常表的方式对异常进行处理。
- 在 JDK 1.4.2 之前的版本中,采用特定的指令方式。
- 当异常处理存在 finally 语句块时,JVM 采取的处理方式是将 finally 语句块的字节码拼接到每一个 catch 块后面
二、方法级异常 throws
public void test() throws FileNotFoundException, RuntimeException {
}
在上述方法上加上 FileNotFoundException, RuntimeException 两个异常重新编译后的字节码,方法多了一个 Exceptions 属性,这个属性和 Code 属性平级。
参考:
- 周志明,深入理解Java虚拟机 - 第 6 章:类文件结构
- Java 反编译工具 - jclasslib(比 javap -v 信息更详细,可以在 IDEA 插件中直接下载)
每天用心记录一点点。内容也许不重要,但习惯很重要!