很多时候我们通过代码无法了解字节码执行过程,比如Try Catch Finally的执行过程,只有通过debug或者阅读字节码才能搞懂JVM是如何编译和设计执行流程的。
public class TestTryCatch {
private String feild = "this is class variables";
public static String staticFeild = "this is class static variables";
public static final String staticFinalFeild = "this is class static final variables";
public volatile String volatileFeild = "this is class volatile variables";
static {
System.out.println("from the class static block");
}
{
System.out.println("from the class block");
}
public TestTryCatch() {
}
public TestTryCatch(String feild) {
this.feild = feild;
}
public static String staticMethod() {
int a = 1;
int b = 10;
a = b;
return "from static method";
}
public synchronized void syncMethod() {
System.out.println("from sync method ");
}
public void syncBlock() {
synchronized (this) {
System.out.println("from sync block");
}
}
public String main(String[] args) {
try {
staticMethod();
syncBlock();
return "from try";
} catch (Exception e) {
e.printStackTrace();
return "from catch";
} finally {
return "from finally";
}
}
}
可以通过$JDKPath$\bin\javap -c -verbose $FileClass$
来编译Java文件,也可以看我的另一篇博客,通过IDEA来查看https://www.jianshu.com/p/738de4e519b6
编译成字节码之后:
分析在后面,比较长,可以直接划过,回头再看
"/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home\bin\javap" -c -verbose com.wei.sample.clazz.TestTryCatch
Classfile /Users/shuxin.wei/Documents/icourtCode/sample/app/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/wei/sample/clazz/TestTryCatch.class
Last modified 2019-5-16; size 1901 bytes
MD5 checksum 00ed76899d96d428782d4d128839aa9f
Compiled from "TestTryCatch.java"
public class com.wei.sample.clazz.TestTryCatch
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #22.#59 // java/lang/Object."<init>":()V
#2 = String #60 // this is class variables
#3 = Fieldref #21.#61 // com/wei/sample/clazz/TestTryCatch.feild:Ljava/lang/String;
#4 = String #62 // this is class volatile variables
#5 = Fieldref #21.#63 // com/wei/sample/clazz/TestTryCatch.volatileFeild:Ljava/lang/String;
#6 = Fieldref #64.#65 // java/lang/System.out:Ljava/io/PrintStream;
#7 = String #66 // from the class block
#8 = Methodref #67.#68 // java/io/PrintStream.println:(Ljava/lang/String;)V
#9 = String #69 // from static method
#10 = String #70 // from sync method
#11 = String #71 // from sync block
#12 = Methodref #21.#72 // com/wei/sample/clazz/TestTryCatch.staticMethod:()Ljava/lang/String;
#13 = String #73 // from try
#14 = String #74 // from finally
#15 = Class #75 // java/lang/Exception
#16 = Methodref #15.#76 // java/lang/Exception.printStackTrace:()V
#17 = String #77 // from catch
#18 = String #78 // this is class static variables
#19 = Fieldref #21.#79 // com/wei/sample/clazz/TestTryCatch.staticFeild:Ljava/lang/String;
#20 = String #80 // from the class static block
#21 = Class #81 // com/wei/sample/clazz/TestTryCatch
#22 = Class #82 // java/lang/Object
#23 = Utf8 feild
#24 = Utf8 Ljava/lang/String;
#25 = Utf8 staticFeild
#26 = Utf8 staticFinalFeild
#27 = Utf8 ConstantValue
#28 = String #83 // this is class static final variables
#29 = Utf8 volatileFeild
#30 = Utf8 <init>
#31 = Utf8 ()V
#32 = Utf8 Code
#33 = Utf8 LineNumberTable
#34 = Utf8 LocalVariableTable
#35 = Utf8 this
#36 = Utf8 Lcom/wei/sample/clazz/TestTryCatch;
#37 = Utf8 (Ljava/lang/String;)V
#38 = Utf8 staticMethod
#39 = Utf8 ()Ljava/lang/String;
#40 = Utf8 a
#41 = Utf8 I
#42 = Utf8 b
#43 = Utf8 syncMethod
#44 = Utf8 syncBlock
#45 = Utf8 StackMapTable
#46 = Class #81 // com/wei/sample/clazz/TestTryCatch
#47 = Class #82 // java/lang/Object
#48 = Class #84 // java/lang/Throwable
#49 = Utf8 main
#50 = Utf8 ([Ljava/lang/String;)Ljava/lang/String;
#51 = Utf8 e
#52 = Utf8 Ljava/lang/Exception;
#53 = Utf8 args
#54 = Utf8 [Ljava/lang/String;
#55 = Class #75 // java/lang/Exception
#56 = Utf8 <clinit>
#57 = Utf8 SourceFile
#58 = Utf8 TestTryCatch.java
#59 = NameAndType #30:#31 // "<init>":()V
#60 = Utf8 this is class variables
#61 = NameAndType #23:#24 // feild:Ljava/lang/String;
#62 = Utf8 this is class volatile variables
#63 = NameAndType #29:#24 // volatileFeild:Ljava/lang/String;
#64 = Class #85 // java/lang/System
#65 = NameAndType #86:#87 // out:Ljava/io/PrintStream;
#66 = Utf8 from the class block
#67 = Class #88 // java/io/PrintStream
#68 = NameAndType #89:#37 // println:(Ljava/lang/String;)V
#69 = Utf8 from static method
#70 = Utf8 from sync method
#71 = Utf8 from sync block
#72 = NameAndType #38:#39 // staticMethod:()Ljava/lang/String;
#73 = Utf8 from try
#74 = Utf8 from finally
#75 = Utf8 java/lang/Exception
#76 = NameAndType #90:#31 // printStackTrace:()V
#77 = Utf8 from catch
#78 = Utf8 this is class static variables
#79 = NameAndType #25:#24 // staticFeild:Ljava/lang/String;
#80 = Utf8 from the class static block
#81 = Utf8 com/wei/sample/clazz/TestTryCatch
#82 = Utf8 java/lang/Object
#83 = Utf8 this is class static final variables
#84 = Utf8 java/lang/Throwable
#85 = Utf8 java/lang/System
#86 = Utf8 out
#87 = Utf8 Ljava/io/PrintStream;
#88 = Utf8 java/io/PrintStream
#89 = Utf8 println
#90 = Utf8 printStackTrace
{
public static java.lang.String staticFeild;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC
public static final java.lang.String staticFinalFeild;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: String this is class static final variables
public volatile java.lang.String volatileFeild;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC, ACC_VOLATILE
public com.wei.sample.clazz.TestTryCatch();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2 // String this is class variables
7: putfield #3 // Field feild:Ljava/lang/String;
10: aload_0
11: ldc #4 // String this is class volatile variables
13: putfield #5 // Field volatileFeild:Ljava/lang/String;
16: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
19: ldc #7 // String from the class block
21: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
24: return
LineNumberTable:
line 24: 0
line 11: 4
line 14: 10
line 21: 16
line 25: 24
LocalVariableTable:
Start Length Slot Name Signature
0 25 0 this Lcom/wei/sample/clazz/TestTryCatch;
public com.wei.sample.clazz.TestTryCatch(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2 // String this is class variables
7: putfield #3 // Field feild:Ljava/lang/String;
10: aload_0
11: ldc #4 // String this is class volatile variables
13: putfield #5 // Field volatileFeild:Ljava/lang/String;
16: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
19: ldc #7 // String from the class block
21: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
24: aload_0
25: aload_1
26: putfield #3 // Field feild:Ljava/lang/String;
29: return
LineNumberTable:
line 27: 0
line 11: 4
line 14: 10
line 21: 16
line 28: 24
line 29: 29
LocalVariableTable:
Start Length Slot Name Signature
0 30 0 this Lcom/wei/sample/clazz/TestTryCatch;
0 30 1 feild Ljava/lang/String;
public static java.lang.String staticMethod();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=0
0: iconst_1
1: istore_0
2: bipush 10
4: istore_1
5: iload_1
6: istore_0
7: ldc #9 // String from static method
9: areturn
LineNumberTable:
line 32: 0
line 33: 2
line 34: 5
line 35: 7
LocalVariableTable:
Start Length Slot Name Signature
2 8 0 a I
5 5 1 b I
public synchronized void syncMethod();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #10 // String from sync method
5: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 39: 0
line 40: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/wei/sample/clazz/TestTryCatch;
public void syncBlock();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #11 // String from sync block
9: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
13: monitorexit
14: goto 22
17: astore_2
18: aload_1
19: monitorexit
20: aload_2
21: athrow
22: return
Exception table:
from to target type
4 14 17 any
17 20 17 any
LineNumberTable:
line 43: 0
line 44: 4
line 45: 12
line 46: 22
LocalVariableTable:
Start Length Slot Name Signature
0 23 0 this Lcom/wei/sample/clazz/TestTryCatch;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 17
locals = [ class com/wei/sample/clazz/TestTryCatch, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
public java.lang.String main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=5, args_size=2
0: invokestatic #12 // Method staticMethod:()Ljava/lang/String;
3: pop
4: ldc #13 // String from try
6: astore_2
7: ldc #14 // String from finally
9: areturn
10: astore_2
11: aload_2
12: invokevirtual #16 // Method java/lang/Exception.printStackTrace:()V
15: ldc #17 // String from catch
17: astore_3
18: ldc #14 // String from finally
20: areturn
21: astore 4
23: ldc #14 // String from finally
25: areturn
Exception table:
from to target type
0 7 10 Class java/lang/Exception
0 7 21 any
10 18 21 any
21 23 21 any
LineNumberTable:
line 50: 0
line 51: 4
line 56: 7
line 52: 10
line 53: 11
line 54: 15
line 56: 18
LocalVariableTable:
Start Length Slot Name Signature
11 10 2 e Ljava/lang/Exception;
0 26 0 this Lcom/wei/sample/clazz/TestTryCatch;
0 26 1 args [Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 74 /* same_locals_1_stack_item */
stack = [ class java/lang/Exception ]
frame_type = 74 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: ldc #18 // String this is class static variables
2: putstatic #19 // Field staticFeild:Ljava/lang/String;
5: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #20 // String from the class static block
10: invokevirtual #8 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: return
LineNumberTable:
line 12: 0
line 17: 5
line 18: 13
}
SourceFile: "TestTryCatch.java"
Process finished with exit code 0
类信息和常量池
Classfile /Users/shuxin.wei/Documents/icourtCode/sample/app/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/wei/sample/clazz/TestTryCatch.class
Last modified 2019-5-16; size 1901 bytes
MD5 checksum 00ed76899d96d428782d4d128839aa9f
Compiled from "TestTryCatch.java"
public class com.wei.sample.clazz.TestTryCatch
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #22.#59 // java/lang/Object."<init>":()V
#2 = String #60 // this is class variables
方法编译成字节码分析,以main方法为例
方法描述
public java.lang.String main(java.lang.String[]);
descriptor:方法参数和返回值描述
descriptor: ([Ljava/lang/String;)Ljava/lang/String;
flags:方法属性描述
flags: ACC_PUBLIC ACC_STATIC ACC_SYNCHONIZE
code:方法栈描述
Code:
stack=1, locals=5, args_size=2
0: invokestatic #12 // Method staticMethod:()Ljava/lang/String;
3: pop
4: ldc #13 // String from try
6: astore_2
7: ldc #14 // String from finally
9: areturn
10: astore_2
11: aload_2
12: invokevirtual #16 // Method java/lang/Exception.printStackTrace:()V
15: ldc #17 // String from catch
17: astore_3
18: ldc #14 // String from finally
20: areturn
21: astore 4
23: ldc #14 // String from finally
25: areturn
stack:栈深度
locals:本地变量个数
args_size:
指令 | 含义 |
---|---|
aload_0 | 这个操作码是aload格式操作码中的一个。它们用来把对象引用加载到操作码栈。 表示正在被访问的局部变量数组的位置,但只能是0、1、2、3 中的一个。还有一些其它类似的操作码用来载入非对象引用的数据,如iload, lload, float 和 dload。其中 i 表示 int,l 表示 long,f 表示 float,d 表示 double。局部变量数组位置大于 3 的局部变量可以用 iload, lload, float, dload 和 aload 载入。这些操作码都只需要一个操作数,即数组中的位置 |
astore_2 | 将引用数据类型赋值后存储在变量表中的下标3位置 |
bipush | 变量压栈:当int取值-1~ 5采用iconst指令,取值-128~ 127采用bipush指令,取值-32768~ 32767采用sipush指令,取值-2147483648~ 2147483647采用 ldc 指令 |
ldc | 这个操作码用来将常量从运行时常量池压栈到操作数栈 |
getstatic | 这个操作码用来把一个静态变量从运行时常量池的静态变量列表中压栈到操作数栈 |
invokespecial, invokevirtual | 这些操作码属于一组函数调用的操作码,包括:invokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual。在这个 class 文件中,invokespecial 和 invokevirutal 两个指令都用到了,两者的区别是,invokevirutal 指令调用一个对象的实例方法,invokespecial 指令调用实例初始化方法、私有方法、父类方法。 |
return | 这个操作码属于ireturn、lreturn、freturn、dreturn、areturn 和 return 操作码组。每个操作码返回一种类型的返回值,其中 i 表示 int,l 表示 long,f 表示 float,d 表示 double,a 表示 对象引用。没有前缀类型字母的 return 表示返回 void |
Exception table:异常跳转表
Exception table:
from to target type
0 7 10 Class java/lang/Exception
0 7 21 any
10 18 21 any
21 23 21 any
从异常表中,可以看到有三种异常情况发生导致执行的路径不同:
第一种:如果位于0到7字节之间的命令(即try块中的代码)抛出了Class java/lang/Exception类型的异常,则跳转到第10个字节开始执行catch中的代码;
第二种:如果位于0到7字节之间的命令(即try块中的代码)抛出了任何类型的异常,则跳转到第21个字节开始执行finally里面的代码。
第三种:如果位于10到18字节之间的命令(即catch块中的代码)跑出了任何类型的异常,则跳转到第21个字节开始执行finally里面的代码。
第四种:如果位于21到23字节之间的命令(即catch块中的代码)跑出了任何类型的异常,则跳转到第21个字节开始执行finally里面的代码。
LineNumberTable:行号表
为调试器提供源码中的每一行对应的字节码信息。上面的例子中,Java 源码里的第 50 行与 TestTryCatch.java 函数字节码序号 0 相关,第 51 行与字节码序号 4 相关
LineNumberTable:
line 50: 0
line 51: 4
line 56: 7
LocalVariableTable:本地变量表
存放局部变量,比如在try中出现异常后,会通过astore_2将Exception存到数组的第3个位置,也就是结合Exception table和LocalVariableTable来完成异常出现时的执行过程;
LocalVariableTable:
Start Length Slot Name Signature
12 10 2 e Ljava/lang/Exception;
0 26 0 this Lcom/wei/sample/clazz/TestTryCatch;
0 26 1 args [Ljava/lang/String;
StackMapTable:栈图
主要用于类加载时校验用的,类加载其中校验这步中会校验字节码是否非法,继承关系,地址偏移是否正确等,不用太关心;
类加载可参考:https://www.jianshu.com/p/071874762b72
StackMapTable具体可参考: https://blog.csdn.net/lijingyao8206/article/details/46715405
StackMapTable: number_of_entries = 2
frame_type = 74 /* same_locals_1_stack_item */
stack = [ class java/lang/Exception ]
frame_type = 74 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]