一、分类
1.存储和加载指令
用于处理 ==操作数栈、局部变量表、常量池==,这三者的调度。指令包含 load、store、ldc、push
2.对象操作指令
用于创建对象和对象读写访问,字段读写访问 getfield、putfield, 静态字段的读写访问 getstaic、putstatic, 对象创建new 指令, 还有 instanceof指令
3.操作数栈管理指令
只用于对操作数栈的管理, 包含pop、dup
4. 类型转换和运算指令
一般只对操作数栈进行操作,包含 add、div、l2i
5.控制跳转指令
包含if系列指令和goto指令
6.方法调用和返回指令
用于方法调用和返回操作,主要包括invoke系列指令和return系列指令, 当调用invoke指令时,会开辟一个新的空间(新的栈和局部变量表)执行指令, 执行return 方法时,结束自己的的空间。
二、执行
从上面的图中, 可以看出基本操作都是围绕这个操作栈进行的,通过压入要出来的目标,执行相应的操作之指令后,出栈。当调用 invoke指令时,创建一个新的空间
三、实例
1. 读操作
//java
public class MainClient {
private int a = 1;
public static void main(String[] args) {
new MainClient().foo();
}
public int foo() {
return this.a;
}
}
//字节码
public int foo();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field a:I
4: ireturn
LineNumberTable:
line 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/daole/datastructure/MainClient;
}
可以看到如下步骤
- aload_0 对应着本地变量表格,是压入一个 MainClicent实例对象
- getfield #2 ,弹出栈顶的目标,获取对应的字段a,并把a压入操作栈。
- ireturn 弹出栈顶目标,并返回,结束空间。
2.写操作
//java
public class MainClient {
private int a = 1;
public static void main(String[] args) {
new MainClient().foo();
}
public void foo() {
this.a = 2;
}
}
//字节码
public void foo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: iconst_2
2: putfield #2 // Field a:I
5: return
LineNumberTable:
line 11: 0
line 12: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/daole/datastructure/MainClient;
可以看到如下步骤
- 同样是压入操作对象
- 压入一个常量 2
- putfield 需要两个参数,于是操作数栈弹出两个参数, 分别对应 aload_0 和 iconst_2 , 获取a字段后进行赋值
- return 结束空间
3.调用操作
//java
public class MainClient {
private int a = 0;
public static void main(String[] args) {
new MainClient().foo();
}
public void foo() {
this.a = 2;
foo2();
}
public void foo2() {
this.a = 3;
}
}
//字节码
public void foo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: iconst_2
2: putfield #2 // Field a:I
5: aload_0
6: invokevirtual #6 // Method foo2:()V
9: return
LineNumberTable:
line 11: 0
line 12: 5
line 13: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/daole/datastructure/MainClient;
public void foo2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: iconst_3
2: putfield #2 // Field a:I
5: return
LineNumberTable:
line 16: 0
line 17: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/daole/datastructure/MainClient;
我们可以看一下有一下几个重要的步骤
- foo: 赋值操作结束后,执行了一次aload_0 压入了 MainClient对象实例,也就是this。
- foo:弹出this, 执行invokevirtual 指令 跳转到新的空间执行 foo2方法
- foo2:执行赋值操作
- foo2: 调用return 结束空间
- foo2: 返回
- foo: 返回