通过字节码分析this关键字以及异常表的重要作用
编写代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class MyTest3 { public void test(){ try { InputStream inputStream = new FileInputStream("test.text"); ServerSocket serverSocket = new ServerSocket(6666); serverSocket.accept(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }catch (Exception ex){
}finally { System.out.println("finally"); }
} }
|
反编译:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| public void test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=3, locals=4 (局部变量是4个), args_size=1 (参数大小是1个,即this) 0: new #2 // class java/io/FileInputStream 3: dup 4: ldc #3 // String test.text 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 6666 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 92 37: astore_1 38: aload_1 39: invokevirtual #12 // Method java/io/FileNotFoundException.printStackTrace:()V 42: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 45: ldc #9 // String finally 47: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 50: goto 92 53: astore_1 54: aload_1 55: invokevirtual #14 // Method java/io/IOException.printStackTrace:()V 58: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 61: ldc #9 // String finally 63: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 66: goto 92 69: astore_1 70: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 73: ldc #9 // String finally 75: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 78: goto 92 81: astore_3 82: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 85: ldc #9 // String finally 87: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 90: aload_3 91: athrow 92: return Exception table: from to target type 0 26 37 Class java/io/FileNotFoundException 0 26 53 Class java/io/IOException 0 26 69 Class java/lang/Exception 0 26 81 any 37 42 81 any 53 58 81 any LineNumberTable: line 36: 0 line 37: 10 line 38: 21 line 46: 26 line 47: 34 line 39: 37 line 40: 38 line 46: 42 line 47: 50 line 41: 53 line 42: 54 line 46: 58 line 47: 66 line 43: 69 line 46: 70 line 47: 78 line 46: 81 line 49: 92 LocalVariableTable: Start Length Slot Name Signature 10 16 1 inputStream Ljava/io/InputStream; 21 5 2 serverSocket Ljava/net/ServerSocket; 38 4 1 e Ljava/io/FileNotFoundException; 54 4 1 e Ljava/io/IOException; 0 93 0 this Lcom/twodragonlake/jvm/bytecode/MyTest3; StackMapTable: number_of_entries = 5 frame_type = 101 /* same_locals_1_stack_item */ stack = [ class java/io/FileNotFoundException ] frame_type = 79 /* same_locals_1_stack_item */ stack = [ class java/io/IOException ] frame_type = 79 /* 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 */
|
字节码中args_size=1,意思是方法参数个数是1个,但是在源代码当中test的参数是空的,原因是:
* 对于java类中的每一个实例方法(非static方法)。其在编译后所生成的字节码当中,方法参数的数量总是
* 会比源代码中方法参数的数量多一个(多了this),它位于方法的第一个参数位置处;这样,我们就可以在java
* 的实例方法中使用this来去访问当前对象的属性以及其他方法。
*
* 这个操作是在编译期间完成的,既由javac编译器在编译的时候将对this的访问转化为对一个普通实例方法参数的访问;
* 接下来在运行期间,由jvm在调用实例方法时,自动向实例方法传入该this参数,所以,在实例方法的局部变量表中,
* 至少会有一个指向当前对象的局部变量。
locals=4 有四个局部变量:this、inputStream、serverSocket、最后我们有三个catch,那么java在运行的时候如果抛出异常,那么只有一个catch
可以进入,catch上都有一个ex变量,所有说第四个局部变量就是某一个ex。
max_stack:
表示这个方法运行的任何时刻所能达到的操作数栈的最大深度。
max_locals表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量。
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表示会被处理的异常类型,他指向常量池里的一个异常类,当catch_type为时,表示处理所有异常。
通过字节码分析Java异常处理机制
在jclasslib插件中我们找到test方法的code的字节码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| 0 new #2 <java/io/FileInputStream> 3 dup 4 ldc #3 <test.text> 6 invokespecial #4 <java/io/FileInputStream.<init>> 9 astore_1 //到此是完成 InputStream inputStream = new FileInputStream("test.text"); 10 new #5 <java/net/ServerSocket> 13 dup 14 sipush 6666 17 invokespecial #6 <java/net/ServerSocket.<init>> 20 astore_2 到此是完成ServerSocket serverSocket = new ServerSocket(6666); 21 aload_2 22 invokevirtual #7 <java/net/ServerSocket.accept> 到此是 serverSocket.accept(); 25 pop 26 getstatic #8 <java/lang/System.out> 29 ldc #9 <finally> 31 invokevirtual #10 <java/io/PrintStream.println> 到此是finally块的打印 34 goto 92 (+58) 37 astore_1 38 aload_1 39 invokevirtual #12 <java/io/FileNotFoundException.printStackTrace> 42 getstatic #8 <java/lang/System.out> 45 ldc #9 <finally> 47 invokevirtual #10 <java/io/PrintStream.println> 50 goto 92 (+42) 53 astore_1 54 aload_1 55 invokevirtual #14 <java/io/IOException.printStackTrace> 58 getstatic #8 <java/lang/System.out> 61 ldc #9 <finally> 63 invokevirtual #10 <java/io/PrintStream.println> 66 goto 92 (+26) 69 astore_1 70 getstatic #8 <java/lang/System.out> 73 ldc #9 <finally> 75 invokevirtual #10 <java/io/PrintStream.println> 78 goto 92 (+14) 81 astore_3 82 getstatic #8 <java/lang/System.out> 85 ldc #9 <finally> 87 invokevirtual #10 <java/io/PrintStream.println> 90 aload_3 91 athrow 92 return
|
行开始会看到有很多goto语句,因为程序在运行的时候才会知道到底跳转到那个catch,所以需要提前罗列出所有的跳转的goto语句,当抛出异常的时候match到某个异常,直接goto到某个catch块。
Exception table:
Nr.0 |
start_pc |
end_pc |
handler_pc |
catch_type |
0 |
0 |
26 |
37 |
cp_info #11 FileNotFoundException |
1 |
0 |
26 |
49 |
cp_info #13 IOException |
2 |
0 |
26 |
61 |
cp_info #15 Exception |
3 |
0 |
26 |
73 |
cp_info #0 |
第一条解释:
从第0行到26行(不包含26)所有的代码某一行如果出现异常就会被捕捉到如果是FileNotFoundException就会goto到37条handler,37是【37 astore_1】 astore_1
是赋值 操作,发生异常时,会将异常对象赋值给FileNotFoundException e的e,让e指向这个抛出的异常对象。然后就是到了finally块:
1 2 3
| 42 getstatic #8 <java/lang/System.out> 45 ldc #9 <finally> 47 invokevirtual #10 <java/io/PrintStream.println>
|
打印输出,然后紧接着是:50 goto 92 (+42) 跳转到92条,92条是【92 return】 也就是程序方法返回。
第二条也是同样的道理,只不过是跳转到IOException 的catch的处理,然后也会执行finally块,然后跳转92条是【92 return】 程序返回。
另外编译器还自动生成了一个Any类型的异常,用于处理其他不可预期的异常处理。同样的这个any类型的也会最终执行finally,然后程序方法返回。即:
|3 |0 |26 |73|cp_info #0 |
- java字节码对于异常的处理方式:
- 1、统一采用异常表的方式对异常进行处理。
- 2、在jdk1.4.2之前的版本中,并不是使用异常表的方式来对异常进行处理的,而是采用特定的指令方式。
- 3、当异常处理在finally语句块时,线代的jvm采取的处理方式是将finally语句块的字节码拼接到每一个catch块后面,
- 换句换说,程序中存在多少个catch块,就会在每一个catch块后面重复多少个finally语句块的字节码。
lineNumberTables:
java字节码和java源代码的行号对应关系,用于调试。
localVariableTable
| Nr.0 | start_pc | length | index | name | descriptor |
| —— | —— | —— | —— | —— |
| 0 |10 |16 |1 |cp_info #24 |cp_info #25 inputStream|
|1 |21 |5 |2 |cp_info #26 |cp_info #27 serverSocket|
|2 |0 |85 |0 |cp_info #21 |cp_info #22 this|
三个局部变量this、serverSocket、inputStream 另外的ex的赋值,需要在运行时才能看到,此处是静态编译无法看到。