我们使用以下简单的代码来学习本章知识
public class MyTest2 {
public void test() {
try {
FileInputStream is = new FileInputStream("test.txt");
ServerSocket socket = new ServerSocket(9999);
socket.accept();
}catch (FileNotFoundException ex){
}catch (IOException ex){
}catch (Exception ex) {
}finally {
System.out.println("finally");
}
}
}
通过javap -verbose
看一下编译的结果, 也就是该java文件反应在字节码之后看看具体的内容
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=4, args_size=1
0: new #2 // class java/io/FileInputStream
3: dup
4: ldc #3 // String test.txt
6: invokespecial #4 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
9: astore_1
10: new #5 // class java/net/ServerSocket
13: dup
14: sipush 9999
17: invokespecial #6 // Method java/net/ServerSocket."<init>":(I)V
20: astore_2
21: aload_2
22: invokevirtual #7 // Method java/net/ServerSocket.accept:()Ljava/net/Socket;
25: pop
26: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
29: ldc #9 // String finally
31: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
34: goto 84
37: astore_1
38: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
41: ldc #9 // String finally
43: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
46: goto 84
49: astore_1
50: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
53: ldc #9 // String finally
55: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
58: goto 84
61: astore_1
62: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
65: ldc #9 // String finally
67: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
70: goto 84
73: astore_3
74: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;
77: ldc #9 // String finally
79: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
82: aload_3
83: athrow
84: return
Exception table:
from to target type
0 26 37 Class java/io/FileNotFoundException
0 26 49 Class java/io/IOException
0 26 61 Class java/lang/Exception
0 26 73 any
LineNumberTable:
line 17: 0
line 19: 10
line 20: 21
line 28: 26
line 29: 34
line 21: 37
line 28: 38
line 29: 46
line 23: 49
line 28: 50
line 29: 58
line 25: 61
line 28: 62
line 29: 70
line 28: 73
line 29: 82
line 30: 84
LocalVariableTable:
Start Length Slot Name Signature
10 16 1 is Ljava/io/FileInputStream;
21 5 2 socket Ljava/net/ServerSocket;
0 85 0 this Lcom/turnsole/myjvm/MyTest2;
StackMapTable: number_of_entries = 5
frame_type = 101 /* same_locals_1_stack_item */
stack = [ class java/io/FileNotFoundException ]
frame_type = 75 /* same_locals_1_stack_item */
stack = [ class java/io/IOException ]
frame_type = 75 /* same_locals_1_stack_item */
stack = [ class java/lang/Exception ]
frame_type = 75 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 10 /* same */
关于常量池之前的内容,以及构造方法,前面一章节的笔记中已经记载的很详细. 此处我们单独把我们写的test()
方法拿出来看一下
1.1 this
关键字
🤔思考
test()
方法明明是没有形参的,为何我们的编译结果还是会有一个形参存在呢?
其实这个问题的答案在前面一章已经给出来了. 虽然我们的
test()
方法没有写任何形参,但是仍然会有一个 JVM创建的形参,并且会作为方法的第一个形参传入,它就是this
对于Java类中的每个实例方法(非static方法),其在编译后所生成的字节码当中,方法参数的数量总是会比源代码中方法参数的数量多一个(
this
), 它位于方法的第一个参数位置处; 这样我们就可以在Java的实例方法中使用this
来访问当前对象的属性以及其他方法;
这个操作是在编译期间完成的,即由javac编译器在编译的时候将对
this
的访问转化为对一个普通实例方法参数的访问,接下来在运行期间,由 JVM 在调用实例方法时, 自动向实例方法传入该this参数. 所以,在实例方法的局部变量表中, 至少会有一个指向当前对象的局部变量;
locals为4, 阅读下我们开始写的代码,思考下分别指的是几个局部变量呢?
自问自答, 分别是
this
,is
,socket
,以及Java在运行过程中可能会出现的一个异常
虽然我们上面写了三个catch代码块(创建了三个异常对象),但是Java语法规定,如果有异常,只能会进入这三个catch中的一个. 因此也就只会有一个异常对象创建出来
1.2 异常表 学习
exception_table, 这里存放的是处理异常的信息, 每个exception_table
表项由start_pc
,end_pc
,handler_pc
,catch_type
组成
start_pc
和end_pc
表示在
code
数组中的从start_pc
到end_pc
处(包含start_pc
,不包含end_pc
)的指令抛出的异常会由这个表项来处理
handler_pc
和catch_type
handler_pc
表示处理异常的代码的开始处.catch_type
表示会被处理的异常类型, 它指向常量池里的一个异常类. 当catch_type
为0时, 表示处理所有的异常.
比方说,从start_pc
到end_pc
这段的代码在执行时出现了异常, 会去找到对应的catch_type
,然后执行handler_pc
起始的字节码(助记符中有一个 goto
表示遇到异常,跳转到多少行代码进行执行)
借助 jclasslib工具,我们看下 JVM对异常的处理
这里
start PC
,end PC
等对应的index值,是对应上上图中Bytecode
红色数字
如第一个是指从 index 0 到 index 26 之间的字节码在执行过程中如果出现了异常
FileNotFoundException
异常,那么执行Handler PC
37 助记符为astore_1
意思是将捕获到的异常对象的引用赋值给ex局部变量,因为我们catch代码块中没有任何操作代码,这个时候👇
38,41,43就是finally代码块中具体的代码执行,首先获取Java.lang.System
的静态变量public final static PrintStream out
,然后助记符ldc
Push item from run-time constant pool 将字符串finally
从运行时常量池中推送出来,然后助记符invokevirtual
调用实例方法,println,最后助记符goto
,跳转到第84个助记号
很明显返回。无返回值。
注意
异常表中,我们发现除了我们代码中自己catch的三个异常之外,还有一个catch_type为any
的异常,这是编译器为我们自动生成的异常。表示处理所有异常,即当catch_type为0时表示所有的异常
1.2.1 Java字节码对于异常的处理方式
- 统一采用异常表的方式来对异常进行处理.
- 在 jdk 1.4.2 之前的版本中,并不是使用异常表的方式来对异常进行处理的, 而是采用特定的指令方式;
- 当异常处理存在
finally
语句块时, 现代化的JVM采用的处理方式是将finally
语句块的字节码拼接到每一个catch块后面,换句话说,程序中存在多少个catch块,就会在每一个catch块后面重复多少个finally
语句块的字节码;
1.2.2 抛出的异常和catch的异常在字节码上的区别
public void test() throws FileNotFoundException, IOException {
try {
FileInputStream is = new FileInputStream("test.txt");
ServerSocket socket = new ServerSocket(9999);
socket.accept();
}catch (FileNotFoundException ex){
}catch (IOException ex){
}catch (Exception ex) {
}finally {
System.out.println("finally");
}
}
很明显能看到,
throws
出来的异常作为和Code
平级存在
其实也不难理解,Code
是方法体中的具体执行代码,catch
也在方法体中, 而throws
则是在方法上的定义