标签:之间 位置 java 4行 als dex item lang name
这一节,我们来分析下异常在字节码文件中的表现,我们来看一下MyTest3:
package com.leolin.jvm.bytecode;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
public class MyTest3 {
public void test() {
try {
InputStream is = new FileInputStream("text.txt");
ServerSocket serverSocket = new ServerSocket(9999);
serverSocket.accept();
} catch (FileNotFoundException ex) {
} catch (IOException ex) {
} catch (Exception ex) {
} finally {
System.out.println("finally");
}
}
}
上面的代码比较简单,就不解释了,我们来看下反编译之后的字节码:
D:\F\java_space\jvm-lecture\target\classes>javap -verbose com.leolin.jvm.bytecode.MyTest3
Classfile /D:/F/java_space/jvm-lecture/target/classes/com/leolin/jvm/bytecode/MyTest3.class
Last modified 2020-5-15; size 1066 bytes
MD5 checksum 68544433621c8ea9d9ba3aa9638949a6
Compiled from "MyTest3.java"
public class com.leolin.jvm.bytecode.MyTest3
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #15.#35 // java/lang/Object."<init>":()V
#2 = Class #36 // java/io/FileInputStream
#3 = String #37 // text.txt
#4 = Methodref #2.#38 // java/io/FileInputStream."<init>":(Ljava/lang/String;)V
#5 = Class #39 // java/net/ServerSocket
#6 = Methodref #5.#40 // java/net/ServerSocket."<init>":(I)V
#7 = Methodref #5.#41 // java/net/ServerSocket.accept:()Ljava/net/Socket;
#8 = Fieldref #42.#43 // java/lang/System.out:Ljava/io/PrintStream;
#9 = String #44 // finally
#10 = Methodref #45.#46 // java/io/PrintStream.println:(Ljava/lang/String;)V
#11 = Class #47 // java/io/FileNotFoundException
#12 = Class #48 // java/io/IOException
#13 = Class #49 // java/lang/Exception
#14 = Class #50 // com/leolin/jvm/bytecode/MyTest3
#15 = Class #51 // java/lang/Object
#16 = Utf8 <init>
#17 = Utf8 ()V
#18 = Utf8 Code
#19 = Utf8 LineNumberTable
#20 = Utf8 LocalVariableTable
#21 = Utf8 this
#22 = Utf8 Lcom/leolin/jvm/bytecode/MyTest3;
#23 = Utf8 test
#24 = Utf8 is
#25 = Utf8 Ljava/io/InputStream;
#26 = Utf8 serverSocket
#27 = Utf8 Ljava/net/ServerSocket;
#28 = Utf8 StackMapTable
#29 = Class #47 // java/io/FileNotFoundException
#30 = Class #48 // java/io/IOException
#31 = Class #49 // java/lang/Exception
#32 = Class #52 // java/lang/Throwable
#33 = Utf8 SourceFile
#34 = Utf8 MyTest3.java
#35 = NameAndType #16:#17 // "<init>":()V
#36 = Utf8 java/io/FileInputStream
#37 = Utf8 text.txt
#38 = NameAndType #16:#53 // "<init>":(Ljava/lang/String;)V
#39 = Utf8 java/net/ServerSocket
#40 = NameAndType #16:#54 // "<init>":(I)V
#41 = NameAndType #55:#56 // accept:()Ljava/net/Socket;
#42 = Class #57 // java/lang/System
#43 = NameAndType #58:#59 // out:Ljava/io/PrintStream;
#44 = Utf8 finally
#45 = Class #60 // java/io/PrintStream
#46 = NameAndType #61:#53 // println:(Ljava/lang/String;)V
#47 = Utf8 java/io/FileNotFoundException
#48 = Utf8 java/io/IOException
#49 = Utf8 java/lang/Exception
#50 = Utf8 com/leolin/jvm/bytecode/MyTest3
#51 = Utf8 java/lang/Object
#52 = Utf8 java/lang/Throwable
#53 = Utf8 (Ljava/lang/String;)V
#54 = Utf8 (I)V
#55 = Utf8 accept
#56 = Utf8 ()Ljava/net/Socket;
#57 = Utf8 java/lang/System
#58 = Utf8 out
#59 = Utf8 Ljava/io/PrintStream;
#60 = Utf8 java/io/PrintStream
#61 = Utf8 println
{
public com.leolin.jvm.bytecode.MyTest3();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/leolin/jvm/bytecode/MyTest3;
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 text.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 12: 0
line 13: 10
line 14: 21
line 19: 26
line 20: 34
line 15: 37
line 19: 38
line 20: 46
line 16: 49
line 19: 50
line 20: 58
line 17: 61
line 19: 62
line 20: 70
line 19: 73
line 21: 84
LocalVariableTable:
Start Length Slot Name Signature
10 16 1 is Ljava/io/InputStream;
21 5 2 serverSocket Ljava/net/ServerSocket;
0 85 0 this Lcom/leolin/jvm/bytecode/MyTest3;
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 */
}
SourceFile: "MyTest3.java"
我们专注在test()方法的Code部分,可以看到,局部变量有4个,而test()的局部变量只有3个。除了我们主动声明的is和serverSocket变量、Java帮我们隐式声明的this变量,这里还多出一个变量,是什么呢?不要忘了,我们还声明了异常对象ex,只是异常对象的具体类型只有在运行期才能确认,所以不在局部变量表中。
在偏移0到22,对应源代码的12到14行。由于这些指令之前都解释过,这里就不再解释了。在偏移22的指令执行完毕之后,原先serverSocket.accept()会将一个Socket对象压入栈中,由于在我们源代码里没有引用这个对象,所以又调用pop指令将这个对象弹出。之后偏移26到31行对应的是源代码的finally块,打印完finally之后就调用34行的goto语句,跳到84行return,整个程序结束。这是正常执行时候的逻辑,但是如果发生了异常呢?
我们来看异常表(Exception Table),这张表有四列:from、to、target和type,每一行的意思代表从偏移from到to(不包含to)的指令,有可能抛出对应type的异常,到异常发生时,需要跳转到偏移target的位置。以第一行为例:从偏移0到偏移26(不包含26),即0到25之间的指令,有可能抛出FileNotFoundException异常,当异常发生时,跳转到偏移37的位置处理异常,同理第二行和第三行的IOException和Exception异常。这里多出一个any,表示处理上面无法处理的异常,一般我们会人为Exception能处理所有的异常,但字节码并不这么认为,所以这里会多出一个any,这是编译器为我们自动生成。
如果我们从37、49、61的指令开始处,都是执行astore_1,这里是把异常对象赋值给局部变量表索引为1的位置,也许会有人疑问局部变量表索引1的位置不是存储is变量吗?其类型是InputStream。其实这种看法只是从Java语法层面上来看,异常对象和InputStream对象是两种不同的类型,但从字节码层面上来看,这两个对象都只是引用而已,而且一旦到达catch块,就意味着is变量的引用已经失效,所以我们可以复用索引1的位置,避免额外的空间开销。
如果我们从每个catch块对应的指令往下看,每个catch块都会跟着finally块中编译后的指令。这意味着在字节码层面,实现finally功能实际上就是在程序正常执行完毕,和在程序抛出异常处理异常之后,都会冗余一块finally代码块对应的指令,以实现不管程序是正常执行完毕还是抛出异常,finally块的代码始终都会执行的效果。
现在,我们不修改test方法内部的代码,不过我们在方法声明处抛出IOException、FileNotFoundException和NullPointerException这三种异常,NullPointerException是运行期异常,一般不需要特别声明,不过这里我们为了做实验特地声明一下:
public void test() throws IOException, FileNotFoundException, NullPointerException {
……
}
我们重新编译MyTest3,看看反编译之后的test()方法:
public void test() throws java.io.IOException, java.io.FileNotFoundException, java.lang.NullPointerException
;
descriptor: ()V
flags: ACC_PUBLIC
Code:
……
Exceptions:
throws java.io.IOException, java.io.FileNotFoundException, java.lang.NullPointerException
Code内容其实和之前一样,不过这里多了一个Exceptions,和Code平级,里面存储我们在test()方法声明处所抛出的异常。
标签:之间 位置 java 4行 als dex item lang name
原文地址:https://www.cnblogs.com/beiluowuzheng/p/12893257.html