一、概述
1、作用
加载和存储指令用于将数据从栈帧的局部变量表和操作数栈之间来回传递。
加载:就是将数据压栈到操作数栈中
存储:将数据保存到局部变量表中
操作码占一个字节,操作数占两个字节
2、常用指令
1、【局部变量压栈指令】将一个局部变量加载到操作数栈: | xload、xload_ |
---|---|
2、【常量入栈指令】将一个常量加载到操作数栈: | bipush、sipush、ldc、ldcw、ldc2_w.aconst_null、iconst_m1、iconst、lconst |
3、【出栈装入局部变量表指令】将一个数值从操作数栈存储到局部变量表: | xstore、xstore_ |
4、扩充局部变量表的访问索引的指令: | wide |
上面所列举的指令助记符中,有一部分是以尖括号结尾的(例如iload
例如:iload_0:将局部变量表中索引为0位置上的数据压入操作数栈中。iload_0只占一个字节,如果是iload 4就是占三个字节
除此之外,它们的语义与原生的通用指令完全一致(例如 iload_0的语义与操作数为0时的 iload指令语义完全一致)。在尖括号之间的字母指定了指令隐含操作数的数据类型,
操作byte、char、short和boolean类型数据时,经常用int类型的指令来表示。
3、操作数栈与局部变量表
1、操作数栈(operand Stacks)
- 我们知道,Java字节码是Java虚拟机所使用的指令集。因此,它与Java虚拟机基于栈的计算模型是密不可分的。在解释执行过程中,每当为Java方法分配栈桢时,Java虚拟机往往需要开辟一块额外的空间作为操作数栈,来存放计的操作数以及返回结果。
- 具体来说便是:执行每一条指令之前,Java虚拟机要求该指令的操作数已被压入操作数栈中。在执行指令时,Java虚拟机会将该指令所需的操作数弹出,并且将指令的结果重新压入栈中。
以加法指令 iadd为例。假设在执行该指令前,栈顶的两个元素分别为 int值1 和 int值 2,那么iadd 指令将弹出这两个int,并将求得的和 int 值3压入栈中。
由于iadd 指令只消耗栈顶的两个元素,因此,对于离栈顶距离为 2 的元素,即图中的问号,iadd 指令并不关心它是否存在,更加不会对其进行修改。
2、局部变量表(Local Variables)
Java 方法栈桢的另外一个重要组成部分则是局部变量区,字节码程序可以将计算的结果缓存在局部变量区之中。实际上,Java虚拟机将局部变量区当成一个数组,依次存放 this指针(仅非静态方法),所传入的参数,以及字节码中的局部变量。
和操作数栈一样,long类型以及double类型的值将占据两个单元,其余类型仅占据一个单元。
举例:public void foo(long l, float f){
{
int i = 0;
}
{
String s = "Hello, World";
}
}
对应的图示:
l 占据两个槽位
i/s 涉及槽位复用
在栈帧中,与性能调优关系最为密切的部分就是局部变量表。局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。
在方法执行时,虚拟机使用局部变量表完成方法的传递。
4、局部变量压栈指令
局部变量压栈指令将给定的局部变量表中的数据压入操作数栈。
这类指令大体可以分为:
√ xload_
√ xload (x为i、l、f、d、a)
说明:在这里,x的取值表示数据类型。
指令xload_n表示将第n个局部变量压入操作数栈,比如iload_1、fload_0、aload_0等指令。其中aload_n表示将个对象引用压栈。
指令xload通过指定参数的形式,把局部变量压入操作数栈,当使用这个命令时,表示局部变量的数量可能超过了4个,比如指令iload、fload等。
package studies.bytecode;
import java.util.Date;
/**
* Created with IntelliJ IDEA.
*
* @author: wuchang
* @Date: 2021/4/27
* @Time: 8:41
* @BelongsPackage studies.bytecode
* @Description: 测试压栈和入栈指令
*/
public class LoadAndStoreTest {
//1.局部变量压栈指令
public void load(int num, Object obj, long count, boolean flag, short[] arr){
System.out.println(num);
System.out.println(obj);
System.out.println(count);
System.out.println(flag);
System.out.println(arr);
}
//2.常量入栈指令
public void pushConstLdc(){
int a = 5;
int b = 6;
int c = 127;
int d = 128;
int e = 1234567;
}
public void constLdc(){
long al = 1;
long a2 = 2;
float b1 = 2;
float b2 = 3;
double c1 = 1;
double c2 = 2;
Date d = null;
}
//3.出栈装入局部变量表指令
public void store(int k, double[] arr){
int m = k + 2;
String str = "Hello Wu";
float f = 10.0F;
arr[0] = 10;
}
}
通过javap -v -p LoadAndStoreTest反编译的字节码文件
Classfile /F:/home/workspace/work/idea/base/eam/bin/studies/bytecode/LoadAndStoreTest.class
Last modified 2021-4-27; size 1444 bytes
MD5 checksum 6130452d132935b91917727aaee24e65
Compiled from "LoadAndStoreTest.java"
public class studies.bytecode.LoadAndStoreTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #18.#64 // java/lang/Object."<init>":()V
#2 = Fieldref #65.#66 // java/lang/System.out:Ljava/io/PrintStream;
#3 = Methodref #67.#68 // java/io/PrintStream.println:(I)V
#4 = Methodref #67.#69 // java/io/PrintStream.println:(Ljava/lang/Object;)V
#5 = Methodref #67.#70 // java/io/PrintStream.println:(J)V
#6 = Methodref #67.#71 // java/io/PrintStream.println:(Z)V
#7 = Integer 1234567
#8 = Long 2l
#10 = Float 3.0f
#11 = Double 2.0d
#13 = String #72 // Hello Wu
#14 = Float 10.0f
#15 = Double 10.0d
#17 = Class #73 // studies/bytecode/LoadAndStoreTest
#18 = Class #74 // java/lang/Object
#19 = Utf8 <init>
#20 = Utf8 ()V
#21 = Utf8 Code
#22 = Utf8 LineNumberTable
#23 = Utf8 LocalVariableTable
#24 = Utf8 this
#25 = Utf8 Lstudies/bytecode/LoadAndStoreTest;
#26 = Utf8 load
#27 = Utf8 (ILjava/lang/Object;JZ[S)V
#28 = Utf8 num
#29 = Utf8 I
#30 = Utf8 obj
#31 = Utf8 Ljava/lang/Object;
#32 = Utf8 count
#33 = Utf8 J
#34 = Utf8 flag
#35 = Utf8 Z
#36 = Utf8 arr
#37 = Utf8 [S
#38 = Utf8 pushConstLdc
#39 = Utf8 a
#40 = Utf8 b
#41 = Utf8 c
#42 = Utf8 d
#43 = Utf8 e
#44 = Utf8 constLdc
#45 = Utf8 al
#46 = Utf8 a2
#47 = Utf8 b1
#48 = Utf8 F
#49 = Utf8 b2
#50 = Utf8 c1
#51 = Utf8 D
#52 = Utf8 c2
#53 = Utf8 Ljava/util/Date;
#54 = Utf8 store
#55 = Utf8 (I[D)V
#56 = Utf8 k
#57 = Utf8 [D
#58 = Utf8 m
#59 = Utf8 str
#60 = Utf8 Ljava/lang/String;
#61 = Utf8 f
#62 = Utf8 SourceFile
#63 = Utf8 LoadAndStoreTest.java
#64 = NameAndType #19:#20 // "<init>":()V
#65 = Class #75 // java/lang/System
#66 = NameAndType #76:#77 // out:Ljava/io/PrintStream;
#67 = Class #78 // java/io/PrintStream
#68 = NameAndType #79:#80 // println:(I)V
#69 = NameAndType #79:#81 // println:(Ljava/lang/Object;)V
#70 = NameAndType #79:#82 // println:(J)V
#71 = NameAndType #79:#83 // println:(Z)V
#72 = Utf8 Hello Wu
#73 = Utf8 studies/bytecode/LoadAndStoreTest
#74 = Utf8 java/lang/Object
#75 = Utf8 java/lang/System
#76 = Utf8 out
#77 = Utf8 Ljava/io/PrintStream;
#78 = Utf8 java/io/PrintStream
#79 = Utf8 println
#80 = Utf8 (I)V
#81 = Utf8 (Ljava/lang/Object;)V
#82 = Utf8 (J)V
#83 = Utf8 (Z)V
{
public studies.bytecode.LoadAndStoreTest();
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 14: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lstudies/bytecode/LoadAndStoreTest;
public void load(int, java.lang.Object, long, boolean, short[]);
descriptor: (ILjava/lang/Object;JZ[S)V
flags: ACC_PUBLIC
Code:
stack=3, locals=7, args_size=6
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: iload_1
4: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
10: aload_2
11: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
14: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
17: lload_3
18: invokevirtual #5 // Method java/io/PrintStream.println:(J)V
21: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
24: iload 5
26: invokevirtual #6 // Method java/io/PrintStream.println:(Z)V
29: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
32: aload 6
34: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
37: return
LineNumberTable:
line 18: 0
line 19: 7
line 20: 14
line 21: 21
line 22: 29
line 23: 37
LocalVariableTable:
Start Length Slot Name Signature
0 38 0 this Lstudies/bytecode/LoadAndStoreTest;
0 38 1 num I
0 38 2 obj Ljava/lang/Object;
0 38 3 count J
0 38 5 flag Z
0 38 6 arr [S
public void pushConstLdc();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=6, args_size=1
0: iconst_5
1: istore_1
2: bipush 6
4: istore_2
5: bipush 127
7: istore_3
8: sipush 128
11: istore 4
13: ldc #7 // int 1234567
15: istore 5
17: return
LineNumberTable:
line 27: 0
line 28: 2
line 29: 5
line 30: 8
line 31: 13
line 32: 17
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 this Lstudies/bytecode/LoadAndStoreTest;
2 16 1 a I
5 13 2 b I
8 10 3 c I
13 5 4 d I
17 1 5 e I
public void constLdc();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=12, args_size=1
0: lconst_1
1: lstore_1
2: ldc2_w #8 // long 2l
5: lstore_3
6: fconst_2
7: fstore 5
9: ldc #10 // float 3.0f
11: fstore 6
13: dconst_1
14: dstore 7
16: ldc2_w #11 // double 2.0d
19: dstore 9
21: aconst_null
22: astore 11
24: return
LineNumberTable:
line 35: 0
line 36: 2
line 37: 6
line 38: 9
line 39: 13
line 40: 16
line 41: 21
line 42: 24
LocalVariableTable:
Start Length Slot Name Signature
0 25 0 this Lstudies/bytecode/LoadAndStoreTest;
2 23 1 al J
6 19 3 a2 J
9 16 5 b1 F
13 12 6 b2 F
16 9 7 c1 D
21 4 9 c2 D
24 1 11 d Ljava/util/Date;
public void store(int, double[]);
descriptor: (I[D)V
flags: ACC_PUBLIC
Code:
stack=4, locals=6, args_size=3
0: iload_1
1: iconst_2
2: iadd
3: istore_3
4: ldc #13 // String Hello Wu
6: astore 4
8: ldc #14 // float 10.0f
10: fstore 5
12: aload_2
13: iconst_0
14: ldc2_w #15 // double 10.0d
17: dastore
18: return
LineNumberTable:
line 46: 0
line 47: 4
line 48: 8
line 49: 12
line 50: 18
LocalVariableTable:
Start Length Slot Name Signature
0 19 0 this Lstudies/bytecode/LoadAndStoreTest;
0 19 1 k I
0 19 2 arr [D
4 15 3 m I
8 11 4 str Ljava/lang/String;
12 7 5 f F
}
SourceFile: "LoadAndStoreTest.java"
load方法的字节码解读:
count占两个槽位
iload_1的意思是将局部变量表中的角标数为1中的num压入操作数栈中,然后操作数栈中的num弹出栈,执行invokevirtual调取pirntln进行打印,然后再执行aload_2将局部变量表中角标数为2的obj压入操作数栈.。。。执行方式跟iload_1一样,以此类推往下执行。
5、常量入栈指令
常量入栈指令的功能是将常数压入操作数栈,根据数据类型和入栈内容的不同,又可以分为const系列、push系列和ldc指令。
指令const系列:用于对特定的常量入栈,入栈的常量隐含在指令本身里。指令有: iconst(i从-1到5)、lconst
比如:
iconst_m1将-1压入操作散栈;
iconst_x(x为0到5)将x压入栈:
lconst_0、lconst_1分别将长整数 0 和 1 压入栈;
fconst_0、fconst_1、fconst_2分别将浮点数 0、1、2压入栈;
dconst_0和dconst_1分别将double型0和1压入栈;
aconst_null将null压入操作数栈;
从指令的命名上不难找出规律,指令助记符的第一个字符总是喜欢表示数据类型,i表示整数,l表示长整数,f表示浮点数,d表示双精度浮点,习惯上用a表示对象引用。如果指令隐含操作的参数,会以下划线形式给出。
例如:
int i = 3; iconst_3;
int j = 6; bipush 6;
指令push系列:主要包括bipush和sipush。它们的区别在于接收数据类型的不同,bipush接收8位整数作为参数,sipush接收16位整数,它们都将参数压入栈。
指令ldc系列:如果以上指令都不能满足需求,那么可以使用万能的ldc指令,它可以接收一个8位的参数,该参数指向常量池中的int、 float或者String的索引,将指定的内容压入堆栈。
类似的还有ldc_w,它接收两个8位参数,能支持的索引范围大于ldc。
如果要压入的元素是long或者double类型的,则使用ldc2_w指令,使用方式都是类似的。
总结如下:
例子:
6、出栈装入局部变量表指令
出栈装入局部变量表指令用于将操作数栈中栈顶元素弹出后,装入局部变量表的指定位置,用于给局部变量赋值。
这类指令主要以store的形式存在,比如xstore (x为i、l、f、d、a)、 xstore_n (x为i、l、f、d、a,n为0至 3)和xastore (x为i、l、f、d、a、b、c、s)。
- 其中,指令istore_n将从操作数栈中弹出一个整数,并把它赋值给局部变量n。
- 指令xstore由于没有隐含参数信息,故需要提供一个byte类型的参数类指定目标局部变量表的位置。
- xastore则专门针对数组操作,以iastore为例,它用于给一个int数组的给定索引赋值。在iastore执行前,操作数栈顶需要以此准备3个元素:值、索引、数组引用,iastore会弹出这3个值,并将值赋给数组中指定索引的位置。
一般说来,类似像store这样的命令需要带一个参数,用来指明将弹出的元素放在局部变量表的第几个位置。但是,为了尽可能压缩指令大小,使用专门的istore_1指令表示将弹出的元素放置在局部变量表第1个位置。类似的还有istore_0、istore_2、istore_3,它们分别表示从操作数栈顶弹出一个元素,存放在局部变量表第0、2、3个位置
由于局部变量表前几个位置总是非常常用,因此这种做法虽然增加了指令数量,但是可以大大压缩生成的字节码的体积。如果局部变量表很大,需要存储的槽位大于3,那么可以使用istore指令,外加一个参数,用来表示需要存放的槽位位置。
举例1:
注意两点:
① 局部变量存储的是具体的值,而不是变量名称,这里只是方便演示所以填入的是引用名,对于基本类型存入的是值,引用类型存入的是引用地址值
② 操作数栈是不断的入栈和出栈操作,比如上面的 iadd 是执行 k+2 操作,操作数栈会将 2 和 k 弹出进行相加后将得出的值(m)压入操作数栈,然后再将值(m)取出放入既不变量表中角标为4的位置。
举例2:
foo方法只有三个槽位,foo方法里有三个变量,但 i 和 s 的作用域在代码块中,所以 i 的槽位被复用了,因为 s 的作用域在代码块中,作用域消失了,所以槽位没有显示,但局部变量表的长度是5,l 占两个槽位