字节码指令
一、javap 工具
Oracle 提供了 javap 工具来反编译 class 文件
// 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)
package jvm.bytecode;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("hello,world");
}
}
hongcaixia@hongcaixiadeMacBook-Pro bytecode % javap -v HelloWorld.class
Classfile /Users/hongcaixia/Documents/work/workspace/demo/target/classes/jvm/bytecode/HelloWorld.class
Last modified 2023-12-22; size 589 bytes
MD5 checksum 4a8677ad7fb4ea31018c87f6c29b4592
Compiled from "HelloWorld.java"
public class jvm.bytecode.HelloWorld
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#21 // java/lang/Object."<init>":()V
#2 = Fieldref #22.#23 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #24 // hello,world
#4 = Methodref #25.#26 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #27 // jvm/bytecode/HelloWorld
#6 = Class #28 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Ljvm/bytecode/HelloWorld;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 MethodParameters
#19 = Utf8 SourceFile
#20 = Utf8 HelloWorld.java
#21 = NameAndType #7:#8 // "<init>":()V
#22 = Class #29 // java/lang/System
#23 = NameAndType #30:#31 // out:Ljava/io/PrintStream;
#24 = Utf8 hello,world
#25 = Class #32 // java/io/PrintStream
#26 = NameAndType #33:#34 // println:(Ljava/lang/String;)V
#27 = Utf8 jvm/bytecode/HelloWorld
#28 = Utf8 java/lang/Object
#29 = Utf8 java/lang/System
#30 = Utf8 out
#31 = Utf8 Ljava/io/PrintStream;
#32 = Utf8 java/io/PrintStream
#33 = Utf8 println
#34 = Utf8 (Ljava/lang/String;)V
{
public jvm.bytecode.HelloWorld();
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 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ljvm/bytecode/HelloWorld;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String hello,world
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 14: 0
line 15: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
MethodParameters:
Name Flags
args
}
SourceFile: "HelloWorld.java"
hongcaixia@hongcaixiadeMacBook-Pro bytecode %
二、方法执行流程
package jvm.bytecode;
/**
* 字节码指令 和 操作数栈、常量池的关系
*/
public class MethodInvokeDemo {
public static void main(String[] args) {
int a = 10;
int b = Short.MAX_VALUE + 1;
int c = a + b;
System.out.println(c); //32778
}
}
hongcaixia@hongcaixiadeMacBook-Pro bytecode % javap -v MethodInvokeDemo.class
Classfile /Users/hongcaixia/Documents/work/workspace/demo/target/classes/jvm/bytecode/MethodInvokeDemo.class
Last modified 2023-12-22; size 666 bytes
MD5 checksum d75beb1197a2a208adbc21ac216fdaf9
Compiled from "MethodInvokeDemo.java"
public class jvm.bytecode.MethodInvokeDemo
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #7.#26 // java/lang/Object."<init>":()V
#2 = Class #27 // java/lang/Short
#3 = Integer 32768
#4 = Fieldref #28.#29 // java/lang/System.out:Ljava/io/PrintStream;
#5 = Methodref #30.#31 // java/io/PrintStream.println:(I)V
#6 = Class #32 // jvm/bytecode/MethodInvokeDemo
#7 = Class #33 // java/lang/Object
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 LocalVariableTable
#13 = Utf8 this
#14 = Utf8 Ljvm/bytecode/MethodInvokeDemo;
#15 = Utf8 main
#16 = Utf8 ([Ljava/lang/String;)V
#17 = Utf8 args
#18 = Utf8 [Ljava/lang/String;
#19 = Utf8 a
#20 = Utf8 I
#21 = Utf8 b
#22 = Utf8 c
#23 = Utf8 MethodParameters
#24 = Utf8 SourceFile
#25 = Utf8 MethodInvokeDemo.java
#26 = NameAndType #8:#9 // "<init>":()V
#27 = Utf8 java/lang/Short
#28 = Class #34 // java/lang/System
#29 = NameAndType #35:#36 // out:Ljava/io/PrintStream;
#30 = Class #37 // java/io/PrintStream
#31 = NameAndType #38:#39 // println:(I)V
#32 = Utf8 jvm/bytecode/MethodInvokeDemo
#33 = Utf8 java/lang/Object
#34 = Utf8 java/lang/System
#35 = Utf8 out
#36 = Utf8 Ljava/io/PrintStream;
#37 = Utf8 java/io/PrintStream
#38 = Utf8 println
#39 = Utf8 (I)V
{
public jvm.bytecode.MethodInvokeDemo();
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 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ljvm/bytecode/MethodInvokeDemo;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: bipush 10
2: istore_1
3: ldc #3 // int 32768
5: istore_2
6: iload_1
7: iload_2
8: iadd
9: istore_3
10: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
13: iload_3
14: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
17: return
LineNumberTable:
line 8: 0
line 9: 3
line 10: 6
line 11: 10
line 12: 17
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 args [Ljava/lang/String;
3 15 1 a I
6 12 2 b I
10 8 3 c I
MethodParameters:
Name Flags
args
}
SourceFile: "MethodInvokeDemo.java"
hongcaixia@hongcaixiadeMacBook-Pro bytecode %
1.常量池载入运行时常量池
image.png整数范围内的数据跟字节码指令一起存储在方法区,超过了范围存储在常量池。
常量池属于方法区的一个部分
2.方法字节码载入方法区
image.png3.main 线程开始运行,分配栈帧内存
stack=2(操作数栈深度),locals=4(局部变量表长度):
image.png
绿色:局部变量表
蓝色:操作数栈
4.执行引擎开始执行字节码
代码 a=10
:
- bipush 10
- istore_1
bipush 10:将一个 byte 压入操作数栈(其长度会补齐 4 个字节),类似的指令还有:
- sipush 将一个 short 压入操作数栈(其长度会补齐 4 个字节)
- ldc 将一个 int 压入操作数栈
- ldc2_w 将一个 long 压入操作数栈(分两次压入,因为 long 是 8 个字节)
image.png这里小的数字都是和字节码指令存一起在方法区,超过 short 范围的数字存入了常量池
istore_1:将操作数栈顶数据弹出,存入局部变量表的 slot 1
代码 int b = Short.MAX_VALUE + 1;
:
- ldc #3
- istore_2
ldc #3:从常量池加载 #3 数据到操作数栈
注意 Short.MAX_VALUE 是 32767,所以 32768 = Short.MAX_VALUE + 1 实际是在编译期间计算好的
istore_2:将操作数栈顶数据弹出,存入局部变量表的 slot 2
代码:int c = a + b;
- iload_1
- iload_2
- iadd
- istore_3
iload_1:将slot 1的值读取到操作数栈
iload_2:局部变量2号槽位的值读取到操作数栈
iadd:弹出操作数栈的两个变量执行加法并且把结果存入操作数栈
istore_3:取出操作数栈的变量存入局部变量表的slot3
代码:System.out.println(c);
- getstatic #4
- iload_3
- invokevirtual #5
getstatic #4:到常量池中找到成员变量的引用,在堆找到System.out对象,将引用放入操作数栈
image.png
iload_3:将局部变量表中slot3的c读入到操作数栈。
invokevirtual #5:
-
找到常量池 #5 项
-
定位到方法区 java/io/PrintStream.println:(I)V 方法
-
生成新的栈帧(分配 locals、stack等)
-
传递参数,执行新栈帧中的字节码
image.png -
执行完毕,弹出栈帧
-
清除 main 操作数栈内容
image.png
return:完成 main 方法调用,弹出 main 栈帧,程序结束
++操作
public class AddDemo {
public static void main(String[] args) {
int a = 10;
int b = a++ + ++a + a--;
System.out.println(a); //11
System.out.println(b); //34
/**
* 局部变量表 操作数栈
* a b
* a++ 11 10
* ++a 12 12
* + 10+12=22
* a-- 11 12
* + 34 22+12=34
*/
}
}
hongcaixia@hongcaixiadeMacBook-Pro bytecode % javap -v AddDemo.class
Classfile /Users/hongcaixia/Documents/work/workspace/demo/target/classes/jvm/bytecode/AddDemo.class
Last modified 2023-12-23; size 614 bytes
MD5 checksum f73913e07ef4806789b63b28b5186492
Compiled from "AddDemo.java"
public class jvm.bytecode.AddDemo
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#23 // java/lang/Object."<init>":()V
#2 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Methodref #26.#27 // java/io/PrintStream.println:(I)V
#4 = Class #28 // jvm/bytecode/AddDemo
#5 = Class #29 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 LocalVariableTable
#11 = Utf8 this
#12 = Utf8 Ljvm/bytecode/AddDemo;
#13 = Utf8 main
#14 = Utf8 ([Ljava/lang/String;)V
#15 = Utf8 args
#16 = Utf8 [Ljava/lang/String;
#17 = Utf8 a
#18 = Utf8 I
#19 = Utf8 b
#20 = Utf8 MethodParameters
#21 = Utf8 SourceFile
#22 = Utf8 AddDemo.java
#23 = NameAndType #6:#7 // "<init>":()V
#24 = Class #30 // java/lang/System
#25 = NameAndType #31:#32 // out:Ljava/io/PrintStream;
#26 = Class #33 // java/io/PrintStream
#27 = NameAndType #34:#35 // println:(I)V
#28 = Utf8 jvm/bytecode/AddDemo
#29 = Utf8 java/lang/Object
#30 = Utf8 java/lang/System
#31 = Utf8 out
#32 = Utf8 Ljava/io/PrintStream;
#33 = Utf8 java/io/PrintStream
#34 = Utf8 println
#35 = Utf8 (I)V
{
public jvm.bytecode.AddDemo();
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 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ljvm/bytecode/AddDemo;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: bipush 10
2: istore_1
3: iload_1
4: iinc 1, 1
7: iinc 1, 1
10: iload_1
11: iadd
12: iload_1
13: iinc 1, -1
16: iadd
17: istore_2
18: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
21: iload_1
22: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
25: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
28: iload_2
29: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
32: return
LineNumberTable:
line 14: 0
line 15: 3
line 16: 18
line 17: 25
line 18: 32
LocalVariableTable:
Start Length Slot Name Signature
0 33 0 args [Ljava/lang/String;
3 30 1 a I
18 15 2 b I
MethodParameters:
Name Flags
args
}
SourceFile: "AddDemo.java"
hongcaixia@hongcaixiadeMacBook-Pro bytecode %
分析:
- iinc 指令是直接在局部变量 slot 上进行运算
- a++ 和 ++a 的区别是先执行 iload 还是 先执行 iinc
int a = 10;
:
- bipush 10:把10放入操作数栈
- istore_1:将10从操作数栈弹出存入局部变量表的槽位1
bipush 10:把10放入操作数栈
image.png
istore_1:将10从操作数栈弹出存入局部变量表的槽位1
image.png
a++
:
- iload_1:将局部变量表1号槽位的10读取到操作数栈中
- iinc 1, 1:将局部变量表1号槽位的数据自增1
iload_1:将局部变量表1号槽位的10读取到操作数栈中
image.png
iinc 1, 1:将局部变量表1号槽位的数据自增1:
image.png
+ ++a
:
- iinc 1, 1:将局部变量表1号槽位的数据自增1
- iload_1:将局部变量表1号槽位的10读取到操作数栈中
- iadd:将操作数栈的两个数据弹出执行加法再将结果22存入操作数栈
iinc 1, 1:将局部变量表1号槽位的数据自增1:
image.png
iload_1:将局部变量表1号槽位的12读取到操作数栈中:
image.png
iadd:将操作数栈的两个数据弹出执行加法再将结果22存入操作数栈
image.png
+ a--
- iload_1:将1号槽位的12放入操作数栈
- iinc 1, -1:将1号槽位的局部变量减1
- iadd:将操作数栈的两个数字相加
iload_1:将1号槽位的12放入操作数栈
image.png
iinc 1, -1:将1号槽位的局部变量减1
image.png
iadd:将操作数栈的两个数字弹出相加再存入操作数栈:
image.png
int b = a++ + ++a + a--; 赋值操作
-
istore_2:将操作数栈的值赋值给2号槽位的局部变量b
image.png
三、条件判断指令
指令 | 助记符 | 含义 |
---|---|---|
0x99 | ifeq | 判断是否 == 0 |
0x9a | ifne | 判断是否 != 0 |
0x9b | iflt | 判断是否 < 0 |
0x9c | ifge | 判断是否 >= 0 |
0x9d | ifgt | 判断是否 > 0 |
0x9e | ifle | 判断是否 <= 0 |
0x9f | if_icmpeq | 两个int是否 == |
0xa0 | if_icmpne | 两个int是否 != |
0xa1 | if_icmplt | 两个int是否 < |
0xa2 | if_icmpge | 两个int是否 >= |
0xa3 | if_icmpgt | 两个int是否 > |
0xa4 | if_icmple | 两个int是否 <= |
0xa5 | if_acmpeq | 两个引用是否 == |
0xa6 | if_acmpne | 两个引用是否 != |
0xc6 | ifnull | 判断是否 == null |
0xc7 | ifnonnull | 判断是否 != null |
- byte,short,char 都会按 int 比较,因为操作数栈都是 4 字节
- goto 用来进行跳转到指定行号的字节码
public class OperateDemo {
public static void main(String[] args) {
int a = 0;
if(a == 0) {
a = 10;
} else {
a = 20;
}
}
}
hongcaixia@hongcaixiadeMacBook-Pro bytecode % javap -v OperateDemo.class
Classfile /Users/hongcaixia/Documents/work/workspace/demo/target/classes/jvm/bytecode/OperateDemo.class
Last modified 2023-12-23; size 510 bytes
MD5 checksum 9b2f54f029ae10e92aed067e468e3c17
Compiled from "OperateDemo.java"
public class jvm.bytecode.OperateDemo
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#21 // java/lang/Object."<init>":()V
#2 = Class #22 // jvm/bytecode/OperateDemo
#3 = Class #23 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Ljvm/bytecode/OperateDemo;
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 args
#14 = Utf8 [Ljava/lang/String;
#15 = Utf8 a
#16 = Utf8 I
#17 = Utf8 StackMapTable
#18 = Utf8 MethodParameters
#19 = Utf8 SourceFile
#20 = Utf8 OperateDemo.java
#21 = NameAndType #4:#5 // "<init>":()V
#22 = Utf8 jvm/bytecode/OperateDemo
#23 = Utf8 java/lang/Object
{
public jvm.bytecode.OperateDemo();
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 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ljvm/bytecode/OperateDemo;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: ifne 12
6: bipush 10
8: istore_1
9: goto 15
12: bipush 20
14: istore_1
15: return
LineNumberTable:
line 14: 0
line 15: 2
line 16: 6
line 18: 12
line 20: 15
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 args [Ljava/lang/String;
2 14 1 a I
StackMapTable: number_of_entries = 2
frame_type = 252 /* append */
offset_delta = 12
locals = [ int ]
frame_type = 2 /* same */
MethodParameters:
Name Flags
args
}
SourceFile: "OperateDemo.java"
hongcaixia@hongcaixiadeMacBook-Pro bytecode %
- 0: iconst_0 将0放入操作数栈
- 1: istore_1 存入1号位
- 2: iload_1:加载1号位到操作数栈
- 3: ifne 12:操作数栈中的数是不是不等于0,满足,跳到12行
- 6: bipush 10:将10放入操作数栈
- 8: istore_1 :将操作数栈中的数赋值给1号槽位
- 9: goto 15:跳转到15行
- 12: bipush 20:将20放入操作数栈
- 14: istore_1 :存入1号位
- 15: return :返回
四、循环控制指令
1.while 循环
public class Demo {
public static void main(String[] args) {
int a = 0;
while (a < 10) {
a++;
}
}
}
0: iconst_0 :声明0
1: istore_1:赋值给1号槽位a变量
2: iload_1: 将1号槽位a变量放入操作数栈
3: bipush 10:10放入操作数栈
5: if_icmpge 14:比较操作数栈的两个变量是否相同,相同则跳转到14行
8: iinc 1, 1:1号槽位的变量自增1
11: goto 2:跳转到第2号
14: return
2.do while 循环
public class Demo {
public static void main(String[] args) {
int a = 0;
do {
a++;
} while (a < 10);
}
}
0: iconst_0
1: istore_1
2: iinc 1, 1
5: iload_1
6: bipush 10
8: if_icmplt 2
11: ret
3.for 循环
public class Demo {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
}
}
}
0: iconst_0
1: istore_1
2: iload_1
3: bipush 10
5: if_icmpge 14
8: iinc 1, 1
11: goto 2
14: return
比较 while 和 for 的字节码,它们是一模一样的
分析
public class CycleDemo {
public static void main(String[] args) {
int i = 0;
int x = 0;
while (i < 10) {
x = x++;
i++;
}
System.out.println(x); // 0
}
}
Code:
stack=2, locals=3, args_size=1
0: iconst_0
1: istore_1
2: iconst_0
3: istore_2
4: iload_1
5: bipush 10
7: if_icmpge 21
10: iload_2
11: iinc 2, 1
14: istore_2
15: iinc 1, 1
18: goto 4
21: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
24: iload_2
25: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
28: return
循环里,局部变量表虽然自增了1,但是赋值操作是把操作数栈中的值赋值给局部变量表,所以最终结果还是0。
x++操作的是局部变量的值,赋值操作是弹出栈中的值
五、构造方法
1.<cinit>()V
public class Cinit {
static {
i = 20;
}
static {
i = 30;
}
static int i = 10;
public static void main(String[] args) {
System.out.println(Cinit.i); // 10
}
}
编译器会按从上至下的顺序,收集所有 static 静态代码块和静态成员赋值的代码,合并为一个特殊的方法 <cinit>()V :
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: bipush 20 //20放入操作数栈
2: putstatic #3 // Field i:I 赋值给常量池中的i变量
5: bipush 30
7: putstatic #3 // Field i:I
10: bipush 10
12: putstatic #3 // Field i:I
15: return
LineNumberTable:
line 13: 0
line 16: 5
line 18: 10
<cinit>()V 方法会在类加载的初始化阶段被调用
2. <init>()V
public class InitDemo {
private String a = "s1";
{
b = 20;
}
private int b = 10;
{
a = "s2";
}
//放在最后
public InitDemo(String a, int b) {
this.a = a;
this.b = b;
}
public static void main(String[] args) {
InitDemo d = new InitDemo("s3", 30);
System.out.println(d.a); //s3
System.out.println(d.b); //30
}
}
编译器会按从上至下的顺序,收集所有 {} 代码块和成员变量赋值的代码,形成新的构造方法,但原始构造方法内的代码总是在最后
public jvm.bytecode.InitDemo(java.lang.String, int);
descriptor: (Ljava/lang/String;I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2 // String s1 s1加载到操作数栈
7: putfield #3 // Field a:Ljava/lang/String; s1赋值给this.a
10: aload_0
11: bipush 20
13: putfield #4 // Field b:I
16: aload_0
17: bipush 10
19: putfield #4 // Field b:I
22: aload_0
23: ldc #5 // String s2
25: putfield #3 // Field a:Ljava/lang/String;
//------------------------------------------------------------------------
28: aload_0
29: aload_1 //a加载到操作数栈
30: putfield #3 // Field a:Ljava/lang/String; //赋值给a
33: aload_0
34: iload_2 //b加载到操作数栈
35: putfield #4 // Field b:I //赋值给b
//------------------------------------------------------------------------
38: return
LineNumberTable:
line 25: 0
line 13: 4
line 16: 10
line 19: 16
line 22: 22
line 26: 28
line 27: 33
line 28: 38
LocalVariableTable:
Start Length Slot Name Signature
0 39 0 this Ljvm/bytecode/InitDemo;
0 39 1 a Ljava/lang/String;
0 39 2 b I
MethodParameters:
六、方法调用
public class MethodInvoke {
public MethodInvoke() { }
private void test1() { }
private final void test2() { }
public void test3() { }
public static void test4() { }
@Override
public String toString() {
return super.toString();
}
public static void main(String[] args) {
MethodInvoke d = new MethodInvoke();
d.test1();
d.test2();
d.test3();
d.test4();
MethodInvoke.test4();
d.toString();
}
}
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #3 // class jvm/bytecode/MethodInvoke
3: dup
4: invokespecial #4 // Method "<init>":()V //构造方法
7: astore_1
8: aload_1
9: invokespecial #5 // Method test1:()V //私有方法
12: aload_1
13: invokespecial #6 // Method test2:()V //final方法
16: aload_1
17: invokevirtual #7 // Method test3:()V //普通方法
20: aload_1
21: pop
22: invokestatic #8 // Method test4:()V //静态方法
25: invokestatic #8 // Method test4:()V //静态方法
28: aload_1
29: invokevirtual #9 // Method toString:()Ljava/lang/String;
32: pop
33: return
LineNumberTable:
- new 是创建【对象】,给对象分配堆内存,执行成功会将【对象引用】压入操作数栈
- dup :是复制操作数栈栈顶的内容,本例即为【对象引用】,需要两份引用,一个是要配合 invokespecial 调用该对象的构造方法 "<init>":()V (会消耗掉栈顶一个引用),另一个要配合 astore_1 赋值给局部变量
- 最终方法(final),私有方法(private),构造方法都是由 invokespecial 指令来调用,属于静态绑定
- 普通成员方法是由 invokevirtual 调用,属于动态绑定,即支持多态
- 成员方法与静态方法调用的另一个区别是,执行方法前是否需要【对象引用】
- d.test4(); 是通过【对象引用】调用一个静态方法,可以看到在调用invokestatic 之前执行了 pop 指令,把【对象引用】从操作数栈弹掉了
- 执行 invokespecial 的情况是通过 super 调用父类方法
七、多态
/**
* @Title: Demo.java
* @Package jvm.bytecode
* @Description: (用一句话描述该文件做什么)
* @Author: hongcaixia
* @Date: 2023/12/23 13:47
* @Version V1.0
* -XX:-UseCompressedOops -XX:-UseCompressedClassPointers
*/
public class Demo {
public static void test(Animal animal) {
animal.eat();
System.out.println(animal.toString());
}
public static void main(String[] args) throws IOException {
test(new Cat());
test(new Dog());
System.in.read();
}
}
abstract class Animal {
public abstract void eat();
@Override
public String toString() {
return "我是" + this.getClass().getSimpleName();
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
1.停在 System.in.read() 方法上,这时运行 jps 获取进程 id
hongcaixia@hongcaixiadeMacBook-Pro demo % jps
95650 RemoteMavenServer36
95637 RemoteMavenServer36
62389 Launcher
62390 Demo
99993 RemoteMavenServer36
65770 HSDB
67372 Jps
95951 RemoteMavenServer36
1598
hongcaixia@hongcaixiadeMacBook-Pro demo %
2.运行 HSDB 工具
进入jdk安装目录执行命令
java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB
hongcaixia@hongcaixiadeMacBook-Pro bytecode % cd /Library/Java/JavaVirtualMachines/jdk1.8.0_271.jdk/Contents/Home
hongcaixia@hongcaixiadeMacBook-Pro Home % java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB
Warning: the font "Times" is not available, so "Lucida Bright" has been substituted, but may have unexpected appearance or behavor. Re-enable the "Times" font to remove this warning.
3.查找某个对象
连上当前进程:62390
File->Attach to hotspot process
image.png
打开 Tools -> Find Object By Query
输入 select d fromjvm.bytecode.Dog d 点击 Execute 执行
得到对象再内存中的地址
4.查看对象内存结构
点击超链接可以看到对象的内存结构,此对象没有任何属性,因此只有对象头的 16 字节,前 8 字节是MarkWord(哈希码和锁标记),后 8 字节就是对象的 Class 类型指针,但目前看不到它的实际地址。
5.查看对象 Class 的内存地址
可以通过 Windows -> Console 进入命令行模式,执行
mem 0x00000001299b4978 2
mem 有两个参数:
- 参数 1 是对象地址
- 参数 2 是查看 2 行(即 16 字节)
结果中第二行 0x000000001b7d4028 即为 Class 的内存地址
image.png image.png
6.查看类的 vtable(多态的方法,虚方法表)
方法1:Alt+R 进入 Inspector 工具,输入刚才的 Class 内存地址,看到如下界面
方法2:或者 Tools -> Class Browser 输入 Dog 查找,可以得到相同的结果
无论通过哪种方法,都可以找到 Dog Class 的 vtable 长度为 6,意思就是 Dog 类有 6 个虚方法(多态相关的,final,static 不会列入)
那么这 6 个方法都是谁,从 Class 的起始地址开始算,偏移 0x1b8 就是 vtable 的起始地址,进行计算得到:
0x000000001b7d4028
1b8 +
---------------------
0x000000001b7d41e0
通过 Windows -> Console 进入命令行模式,执行
mem 0x000000001b7d41e0 6
0x000000001b7d41e0: 0x000000001b3d1b10
0x000000001b7d41e8: 0x000000001b3d15e8
0x000000001b7d41f0: 0x000000001b7d35e8
0x000000001b7d41f8: 0x000000001b3d1540
0x000000001b7d4200: 0x000000001b3d1678
0x000000001b7d4208: 0x000000001b7d3fa8
就得到了 6 个虚方法的入口地址
7.验证方法地址
image.png通过 Tools -> Class Browser 查看每个类的方法定义,比较可知
Dog - public void eat() @0x000000001b7d3fa8
Animal - public java.lang.String toString() @0x000000001b7d35e8;
Object - protected void finalize() @0x000000001b3d1b10;
Object - public boolean equals(java.lang.Object) @0x000000001b3d15e8;
Object - public native int hashCode() @0x000000001b3d1540;
Object - protected native java.lang.Object clone() @0x000000001b3d1678;
eat() 方法是 Dog 类自己的
toString() 方法是继承 String 类的
finalize() ,equals(),hashCode(),clone() 都是继承 Object 类的
总结
当执行 invokevirtual 指令时,
- 先通过栈帧中的对象引用找到对象
- 分析对象头,找到对象的实际 Class
- Class 结构中有 vtable,它在类加载的链接阶段就已经根据方法的重写规则生成好了
- 查表得到方法的具体地址
- 执行方法的字节码
八、异常
1.单个catch
public class ExceptionDemo {
public static void main(String[] args) {
int i = 0;
try {
i = 10;
} catch (Exception e) {
i = 20;
}
}
}
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=1
0: iconst_0
1: istore_1
2: bipush 10
4: istore_1
5: goto 12
8: astore_2
9: bipush 20
11: istore_1
12: return
Exception table:
from to target type
2 5 8 Class java/lang/Exception //检测第2行到第5行的代码 (不包含5),如果发生了异常,进入第8行
LineNumberTable:
line 14: 0
line 16: 2
line 19: 5
line 17: 8
line 18: 9
line 20: 12
LocalVariableTable:
Start Length Slot Name Signature
9 3 2 e Ljava/lang/Exception;
0 13 0 args [Ljava/lang/String;
2 11 1 i I
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 8
locals = [ class "[Ljava/lang/String;", int ]
stack = [ class java/lang/Exception ]
frame_type = 3 /* same */
MethodParameters:
Name Flags
args
}
SourceFile: "ExceptionDemo.java"
hongcaixia@hongcaixiadeMacBook-Pro bytecode %
可以看到多出来一个 Exception table 的结构,[from, to) 是前闭后开的检测范围,一旦这个范围内的字节码执行出现异常,则通过 type 匹配异常类型,如果一致,进入 target 所指示行号8 行的字节码指令 astore_2 是将异常对象引用存入局部变量表的 slot 2 位置
2.多个catch
public class ManyExceptionDemo {
public static void main(String[] args) {
int i = 0;
try {
i = 10;
} catch (ArithmeticException e) {
i = 30;
} catch (NullPointerException e) {
i = 40;
} catch (Exception e) {
i = 50;
}
}
}
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=1
0: iconst_0
1: istore_1
2: bipush 10
4: istore_1
5: goto 26
8: astore_2
9: bipush 30
11: istore_1
12: goto 26
15: astore_2
16: bipush 40
18: istore_1
19: goto 26
22: astore_2
23: bipush 50
25: istore_1
26: return
Exception table:
from to target type
2 5 8 Class java/lang/ArithmeticException
2 5 15 Class java/lang/NullPointerException
2 5 22 Class java/lang/Exception
LineNumberTable:
line 14: 0
line 16: 2
line 23: 5
line 17: 8
line 18: 9
line 23: 12
line 19: 15
line 20: 16
line 23: 19
line 21: 22
line 22: 23
line 24: 26
LocalVariableTable:
Start Length Slot Name Signature
9 3 2 e Ljava/lang/ArithmeticException;
16 3 2 e Ljava/lang/NullPointerException;
23 3 2 e Ljava/lang/Exception;
0 27 0 args [Ljava/lang/String;
2 25 1 i I
StackMapTable: number_of_entries = 4
frame_type = 255 /* full_frame */
offset_delta = 8
locals = [ class "[Ljava/lang/String;", int ]
stack = [ class java/lang/ArithmeticException ]
frame_type = 70 /* same_locals_1_stack_item */
stack = [ class java/lang/NullPointerException ]
frame_type = 70 /* same_locals_1_stack_item */
stack = [ class java/lang/Exception ]
frame_type = 3 /* same */
MethodParameters:
Name Flags
args
}
SourceFile: "ManyExceptionDemo.java"
hongcaixia@hongcaixiadeMacBook-Pro bytecode %
因为异常出现时,只能进入 Exception table 中一个分支,所以局部变量表 slot 2 位置被共用
3.multi-catch
public class MultiCatchDemo {
public static void main(String[] args) {
try {
Method test = MultiCatchDemo.class.getMethod("test");
test.invoke(null);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
public static void test() {
System.out.println("ok");
}
}
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: ldc #2 // class jvm/bytecode/MultiCatchDemo
2: ldc #3 // String test
4: iconst_0
5: anewarray #4 // class java/lang/Class
8: invokevirtual #5 // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
11: astore_1
12: aload_1
13: aconst_null
14: iconst_0
15: anewarray #6 // class java/lang/Object
18: invokevirtual #7 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
21: pop
22: goto 30
25: astore_1
26: aload_1
27: invokevirtual #11 // Method java/lang/ReflectiveOperationException.printStackTrace:()V
30: return
Exception table:
from to target type
0 22 25 Class java/lang/NoSuchMethodException
0 22 25 Class java/lang/IllegalAccessException
0 22 25 Class java/lang/reflect/InvocationTargetException
LineNumberTable:
line 18: 0
line 19: 12
line 22: 22
line 20: 25
line 21: 26
line 23: 30
LocalVariableTable:
Start Length Slot Name Signature
12 10 1 test Ljava/lang/reflect/Method;
26 4 1 e Ljava/lang/ReflectiveOperationException;
0 31 0 args [Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 89 /* same_locals_1_stack_item */
stack = [ class java/lang/ReflectiveOperationException ]
frame_type = 4 /* same */
MethodParameters:
Name Flags
args
九、finally
public class FinallyDemo {
public static void main(String[] args) {
int i = 0;
try {
i = 10;
} catch (Exception e) {
i = 20;
} finally {
i = 30;
}
}
}
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=4, args_size=1
0: iconst_0
1: istore_1 // 0 -> i
2: bipush 10. // try
4: istore_1 // 10 -> i
5: bipush 30 // finally
7: istore_1 // 30 -> i
8: goto 27. // return
11: astore_2 // catch Exceptin -> e
12: bipush 20
14: istore_1 // 20 -> i
15: bipush 30 // finally
17: istore_1 // 30 -> i
18: goto 27. // return
21: astore_3 // catch any -> slot 3
22: bipush 30. // finally
24: istore_1 // 30 -> i
25: aload_3 // <- slot 3
26: athrow // throw
27: return
Exception table:
from to target type
2 5 11 Class java/lang/Exception
2 5 21 any // 剩余的异常类型,比如 Error
11 15 21 any // 剩余的异常类型,比如 Error
LineNumberTable:
line 14: 0
line 16: 2
line 20: 5
line 21: 8
line 17: 11
line 18: 12
line 20: 15
line 21: 18
line 20: 21
line 21: 25
line 22: 27
LocalVariableTable:
Start Length Slot Name Signature
12 3 2 e Ljava/lang/Exception;
0 28 0 args [Ljava/lang/String;
2 26 1 i I
StackMapTable: number_of_entries = 3
frame_type = 255 /* full_frame */
offset_delta = 11
locals = [ class "[Ljava/lang/String;", int ]
stack = [ class java/lang/Exception ]
frame_type = 73 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 5 /* same */
MethodParameters:
Name Flags
args
}
因为finally中的代码一定会被执行,所以 finally 中的代码被复制了 3 份,分别放入 try 流程,catch 流程以及 catch 剩余的异常类型流程
1.finally 出现了 return
public class FinallyReturnDemo {
public static void main(String[] args) {
int result = test();
System.out.println(result); //20
}
public static int test() {
try {
return 10;
} finally {
return 20;
}
}
}
public static int test();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=0
0: bipush 10 //<- 10 放入栈顶
2: istore_0 // 10 -> slot 0 (从栈顶移除了)
3: bipush 20 // <- 20 放入栈顶
5: ireturn // 返回栈顶 int(20)
6: astore_1 // catch any -> slot 1
7: bipush 20 //<- 20 放入栈顶
9: ireturn //返回栈顶 int(20)
Exception table:
from to target type
0 3 6 any
LineNumberTable:
line 20: 0
line 22: 3
StackMapTable: number_of_entries = 1
frame_type = 70 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
}
- 由于 finally 中的 ireturn 被插入了所有可能的流程,因此返回结果肯定以 finally 的为准
- 跟上例中的 finally 相比,发现没有 athrow 了,这告诉我们:如果在 finally 中出现了 return,会吞掉异常
public class FinallyReturnDemo {
public static void main(String[] args) {
int result = test();
System.out.println(result); //20
}
public static int test() {
try {
int i = 1/0;
return 10;
} finally {
return 20;
}
}
}
正常执行,打印20
2.finally 对返回值影响
public class FinallyReturnInfluDemo {
public static void main(String[] args) {
int result = test();
System.out.println(result); //10
}
public static int test() {
int i = 10;
try {
return i;
} finally {
i = 20;
}
}
}
public static int test();
descriptor: ()I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=3, args_size=0
0: bipush 10 //<- 10 放入栈顶
2: istore_0 //10 -> i
3: iload_0 //<- i(10)
4: istore_1 //10 -> slot 1,暂存至 slot 1,目的是为了固定返回值
5: bipush 20 //<- 20 放入栈顶
7: istore_0 //20 -> i
8: iload_1 //<- slot 1(10) 载入 slot 1 暂存的值
9: ireturn //返回栈顶的 int(10)
10: astore_2
11: bipush 20
13: istore_0
14: aload_2
15: athrow
Exception table:
from to target type
3 5 10 any
LineNumberTable:
line 18: 0
line 20: 3
line 22: 5
line 20: 8
line 22: 10
line 23: 14
LocalVariableTable:
Start Length Slot Name Signature
3 13 0 i I
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 10
locals = [ int ]
stack = [ class java/lang/Throwable ]
}
在return之前做了暂存(istore_1),所以finally块对try中的改变不会受影响。finally中没有return,异常可以正常抛出。
十、Synchronized
public class SynchronizedDemo {
public static void main(String[] args) {
Object lock = new Object();
synchronized (lock) {
System.out.println("ok");
}
}
}
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: new #2 // class java/lang/Object //new Object
3: dup //复制一份 一份给构造方法用
4: invokespecial #1 // Method java/lang/Object."<init>":()V //invokespecial <init>:()V
7: astore_1 // lock引用 -> lock
8: aload_1 // <- lock (synchronized开始)
9: dup. //复制一份 一份给加锁,一份给解锁
10: astore_2 // lock引用 -> slot 2
11: monitorenter // monitorenter(lock引用)
12: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
15: ldc #4 // String ok
17: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: aload_2 // <- slot 2(lock引用)
21: monitorexit // monitorexit(lock引用)
22: goto 30
25: astore_3 // any -> slot 3
26: aload_2 // <- slot 2(lock引用)
27: monitorexit // monitorexit(lock引用)
28: aload_3
29: athrow
30: return
Exception table:
from to target type
12 22 25 any
25 28 25 any
LineNumberTable:
line 14: 0
line 15: 8
line 16: 12
line 17: 20
line 18: 30
LocalVariableTable:
Start Length Slot Name Signature
0 31 0 args [Ljava/lang/String;
8 23 1 lock Ljava/lang/Object;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 25
locals = [ class "[Ljava/lang/String;", class java/lang/Object, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
MethodParameters:
Name Flags
args
}
方法级别的 synchronized 不会在字节码指令中有所体现