一、概述

1、作用

加载和存储指令用于将数据从栈帧的局部变量表和操作数栈之间来回传递。
加载:就是将数据压栈到操作数栈中
存储:将数据保存到局部变量表中
操作码占一个字节,操作数占两个字节

2、常用指令

1、【局部变量压栈指令】将一个局部变量加载到操作数栈: xload、xload_( 其中x为i、l、f、d、a, n为 0 到 3 )
2、【常量入栈指令】将一个常量加载到操作数栈: bipush、sipush、ldc、ldcw、ldc2_w.aconst_null、iconst_m1、iconst、lconst、fconst、dconst_
3、【出栈装入局部变量表指令】将一个数值从操作数栈存储到局部变量表: xstore、xstore_(其中x为i、l、f、d、a,n为0到3) ; xastore(其中x为i、l、f、d、a、b、c、s)
4、扩充局部变量表的访问索引的指令: wide

上面所列举的指令助记符中,有一部分是以尖括号结尾的(例如iload)。这些指令助记符实际上代表了一组指令(例如 iload代表了iload_0、iload_1、iload_2和iload_3这几个指令)。这几组指令都是某个带有一个操作数的通用指令(例如iload〉的特殊形式,对于这若干组特殊指令来说,它们表面上没有操作数,不需要进行取操作数的动作,但操作数都隐含在指令中。

例如:iload_0:将局部变量表中索引为0位置上的数据压入操作数栈中。iload_0只占一个字节,如果是iload 4就是占三个字节

除此之外,它们的语义与原生的通用指令完全一致(例如 iload_0的语义与操作数为0时的 iload指令语义完全一致)。在尖括号之间的字母指定了指令隐含操作数的数据类型,代表非负的整数, 〈i〉代表是int类型数据,代表long类型,代表float类型,代表double类型。a代表对象引用类型

操作byte、char、short和boolean类型数据时,经常用int类型的指令来表示。

3、操作数栈与局部变量表

1、操作数栈(operand Stacks)

  • 我们知道,Java字节码是Java虚拟机所使用的指令集。因此,它与Java虚拟机基于栈的计算模型是密不可分的。在解释执行过程中,每当为Java方法分配栈桢时,Java虚拟机往往需要开辟一块额外的空间作为操作数栈,来存放计的操作数以及返回结果。
  • 具体来说便是:执行每一条指令之前,Java虚拟机要求该指令的操作数已被压入操作数栈中。在执行指令时,Java虚拟机会将该指令所需的操作数弹出,并且将指令的结果重新压入栈中。

image.png
以加法指令 iadd为例。假设在执行该指令前,栈顶的两个元素分别为 int值1 和 int值 2,那么iadd 指令将弹出这两个int,并将求得的和 int 值3压入栈中。
image.png
由于iadd 指令只消耗栈顶的两个元素,因此,对于离栈顶距离为 2 的元素,即图中的问号,iadd 指令并不关心它是否存在,更加不会对其进行修改。

2、局部变量表(Local Variables)

Java 方法栈桢的另外一个重要组成部分则是局部变量区,字节码程序可以将计算的结果缓存在局部变量区之中。实际上,Java虚拟机将局部变量区当成一个数组,依次存放 this指针(仅非静态方法),所传入的参数,以及字节码中的局部变量。
和操作数栈一样,long类型以及double类型的值将占据两个单元,其余类型仅占据一个单元。
image.png
举例:
public void foo(long l, float f){
{
int i = 0;
}
{
String s = "Hello, World";
}
}
对应的图示:
l 占据两个槽位
i/s 涉及槽位复用
image.png
在栈帧中,与性能调优关系最为密切的部分就是局部变量表。局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。
在方法执行时,虚拟机使用局部变量表完成方法的传递。

4、局部变量压栈指令

局部变量压栈指令将给定的局部变量表中的数据压入操作数栈。

这类指令大体可以分为:
√ xload_ (x为i、l、f、d、a,n为 0 到 3)
√ xload (x为i、l、f、d、a)
说明:在这里,x的取值表示数据类型。

指令xload_n表示将第n个局部变量压入操作数栈,比如iload_1、fload_0、aload_0等指令。其中aload_n表示将个对象引用压栈。
指令xload通过指定参数的形式,把局部变量压入操作数栈,当使用这个命令时,表示局部变量的数量可能超过了4个,比如指令iload、fload等。

  1. package studies.bytecode;
  2. import java.util.Date;
  3. /**
  4. * Created with IntelliJ IDEA.
  5. *
  6. * @author: wuchang
  7. * @Date: 2021/4/27
  8. * @Time: 8:41
  9. * @BelongsPackage studies.bytecode
  10. * @Description: 测试压栈和入栈指令
  11. */
  12. public class LoadAndStoreTest {
  13. //1.局部变量压栈指令
  14. public void load(int num, Object obj, long count, boolean flag, short[] arr){
  15. System.out.println(num);
  16. System.out.println(obj);
  17. System.out.println(count);
  18. System.out.println(flag);
  19. System.out.println(arr);
  20. }
  21. //2.常量入栈指令
  22. public void pushConstLdc(){
  23. int a = 5;
  24. int b = 6;
  25. int c = 127;
  26. int d = 128;
  27. int e = 1234567;
  28. }
  29. public void constLdc(){
  30. long al = 1;
  31. long a2 = 2;
  32. float b1 = 2;
  33. float b2 = 3;
  34. double c1 = 1;
  35. double c2 = 2;
  36. Date d = null;
  37. }
  38. //3.出栈装入局部变量表指令
  39. public void store(int k, double[] arr){
  40. int m = k + 2;
  41. String str = "Hello Wu";
  42. float f = 10.0F;
  43. arr[0] = 10;
  44. }
  45. }

通过javap -v -p LoadAndStoreTest反编译的字节码文件

  1. Classfile /F:/home/workspace/work/idea/base/eam/bin/studies/bytecode/LoadAndStoreTest.class
  2. Last modified 2021-4-27; size 1444 bytes
  3. MD5 checksum 6130452d132935b91917727aaee24e65
  4. Compiled from "LoadAndStoreTest.java"
  5. public class studies.bytecode.LoadAndStoreTest
  6. minor version: 0
  7. major version: 52
  8. flags: ACC_PUBLIC, ACC_SUPER
  9. Constant pool:
  10. #1 = Methodref #18.#64 // java/lang/Object."<init>":()V
  11. #2 = Fieldref #65.#66 // java/lang/System.out:Ljava/io/PrintStream;
  12. #3 = Methodref #67.#68 // java/io/PrintStream.println:(I)V
  13. #4 = Methodref #67.#69 // java/io/PrintStream.println:(Ljava/lang/Object;)V
  14. #5 = Methodref #67.#70 // java/io/PrintStream.println:(J)V
  15. #6 = Methodref #67.#71 // java/io/PrintStream.println:(Z)V
  16. #7 = Integer 1234567
  17. #8 = Long 2l
  18. #10 = Float 3.0f
  19. #11 = Double 2.0d
  20. #13 = String #72 // Hello Wu
  21. #14 = Float 10.0f
  22. #15 = Double 10.0d
  23. #17 = Class #73 // studies/bytecode/LoadAndStoreTest
  24. #18 = Class #74 // java/lang/Object
  25. #19 = Utf8 <init>
  26. #20 = Utf8 ()V
  27. #21 = Utf8 Code
  28. #22 = Utf8 LineNumberTable
  29. #23 = Utf8 LocalVariableTable
  30. #24 = Utf8 this
  31. #25 = Utf8 Lstudies/bytecode/LoadAndStoreTest;
  32. #26 = Utf8 load
  33. #27 = Utf8 (ILjava/lang/Object;JZ[S)V
  34. #28 = Utf8 num
  35. #29 = Utf8 I
  36. #30 = Utf8 obj
  37. #31 = Utf8 Ljava/lang/Object;
  38. #32 = Utf8 count
  39. #33 = Utf8 J
  40. #34 = Utf8 flag
  41. #35 = Utf8 Z
  42. #36 = Utf8 arr
  43. #37 = Utf8 [S
  44. #38 = Utf8 pushConstLdc
  45. #39 = Utf8 a
  46. #40 = Utf8 b
  47. #41 = Utf8 c
  48. #42 = Utf8 d
  49. #43 = Utf8 e
  50. #44 = Utf8 constLdc
  51. #45 = Utf8 al
  52. #46 = Utf8 a2
  53. #47 = Utf8 b1
  54. #48 = Utf8 F
  55. #49 = Utf8 b2
  56. #50 = Utf8 c1
  57. #51 = Utf8 D
  58. #52 = Utf8 c2
  59. #53 = Utf8 Ljava/util/Date;
  60. #54 = Utf8 store
  61. #55 = Utf8 (I[D)V
  62. #56 = Utf8 k
  63. #57 = Utf8 [D
  64. #58 = Utf8 m
  65. #59 = Utf8 str
  66. #60 = Utf8 Ljava/lang/String;
  67. #61 = Utf8 f
  68. #62 = Utf8 SourceFile
  69. #63 = Utf8 LoadAndStoreTest.java
  70. #64 = NameAndType #19:#20 // "<init>":()V
  71. #65 = Class #75 // java/lang/System
  72. #66 = NameAndType #76:#77 // out:Ljava/io/PrintStream;
  73. #67 = Class #78 // java/io/PrintStream
  74. #68 = NameAndType #79:#80 // println:(I)V
  75. #69 = NameAndType #79:#81 // println:(Ljava/lang/Object;)V
  76. #70 = NameAndType #79:#82 // println:(J)V
  77. #71 = NameAndType #79:#83 // println:(Z)V
  78. #72 = Utf8 Hello Wu
  79. #73 = Utf8 studies/bytecode/LoadAndStoreTest
  80. #74 = Utf8 java/lang/Object
  81. #75 = Utf8 java/lang/System
  82. #76 = Utf8 out
  83. #77 = Utf8 Ljava/io/PrintStream;
  84. #78 = Utf8 java/io/PrintStream
  85. #79 = Utf8 println
  86. #80 = Utf8 (I)V
  87. #81 = Utf8 (Ljava/lang/Object;)V
  88. #82 = Utf8 (J)V
  89. #83 = Utf8 (Z)V
  90. {
  91. public studies.bytecode.LoadAndStoreTest();
  92. descriptor: ()V
  93. flags: ACC_PUBLIC
  94. Code:
  95. stack=1, locals=1, args_size=1
  96. 0: aload_0
  97. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  98. 4: return
  99. LineNumberTable:
  100. line 14: 0
  101. LocalVariableTable:
  102. Start Length Slot Name Signature
  103. 0 5 0 this Lstudies/bytecode/LoadAndStoreTest;
  104. public void load(int, java.lang.Object, long, boolean, short[]);
  105. descriptor: (ILjava/lang/Object;JZ[S)V
  106. flags: ACC_PUBLIC
  107. Code:
  108. stack=3, locals=7, args_size=6
  109. 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
  110. 3: iload_1
  111. 4: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
  112. 7: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
  113. 10: aload_2
  114. 11: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
  115. 14: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
  116. 17: lload_3
  117. 18: invokevirtual #5 // Method java/io/PrintStream.println:(J)V
  118. 21: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
  119. 24: iload 5
  120. 26: invokevirtual #6 // Method java/io/PrintStream.println:(Z)V
  121. 29: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
  122. 32: aload 6
  123. 34: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
  124. 37: return
  125. LineNumberTable:
  126. line 18: 0
  127. line 19: 7
  128. line 20: 14
  129. line 21: 21
  130. line 22: 29
  131. line 23: 37
  132. LocalVariableTable:
  133. Start Length Slot Name Signature
  134. 0 38 0 this Lstudies/bytecode/LoadAndStoreTest;
  135. 0 38 1 num I
  136. 0 38 2 obj Ljava/lang/Object;
  137. 0 38 3 count J
  138. 0 38 5 flag Z
  139. 0 38 6 arr [S
  140. public void pushConstLdc();
  141. descriptor: ()V
  142. flags: ACC_PUBLIC
  143. Code:
  144. stack=1, locals=6, args_size=1
  145. 0: iconst_5
  146. 1: istore_1
  147. 2: bipush 6
  148. 4: istore_2
  149. 5: bipush 127
  150. 7: istore_3
  151. 8: sipush 128
  152. 11: istore 4
  153. 13: ldc #7 // int 1234567
  154. 15: istore 5
  155. 17: return
  156. LineNumberTable:
  157. line 27: 0
  158. line 28: 2
  159. line 29: 5
  160. line 30: 8
  161. line 31: 13
  162. line 32: 17
  163. LocalVariableTable:
  164. Start Length Slot Name Signature
  165. 0 18 0 this Lstudies/bytecode/LoadAndStoreTest;
  166. 2 16 1 a I
  167. 5 13 2 b I
  168. 8 10 3 c I
  169. 13 5 4 d I
  170. 17 1 5 e I
  171. public void constLdc();
  172. descriptor: ()V
  173. flags: ACC_PUBLIC
  174. Code:
  175. stack=2, locals=12, args_size=1
  176. 0: lconst_1
  177. 1: lstore_1
  178. 2: ldc2_w #8 // long 2l
  179. 5: lstore_3
  180. 6: fconst_2
  181. 7: fstore 5
  182. 9: ldc #10 // float 3.0f
  183. 11: fstore 6
  184. 13: dconst_1
  185. 14: dstore 7
  186. 16: ldc2_w #11 // double 2.0d
  187. 19: dstore 9
  188. 21: aconst_null
  189. 22: astore 11
  190. 24: return
  191. LineNumberTable:
  192. line 35: 0
  193. line 36: 2
  194. line 37: 6
  195. line 38: 9
  196. line 39: 13
  197. line 40: 16
  198. line 41: 21
  199. line 42: 24
  200. LocalVariableTable:
  201. Start Length Slot Name Signature
  202. 0 25 0 this Lstudies/bytecode/LoadAndStoreTest;
  203. 2 23 1 al J
  204. 6 19 3 a2 J
  205. 9 16 5 b1 F
  206. 13 12 6 b2 F
  207. 16 9 7 c1 D
  208. 21 4 9 c2 D
  209. 24 1 11 d Ljava/util/Date;
  210. public void store(int, double[]);
  211. descriptor: (I[D)V
  212. flags: ACC_PUBLIC
  213. Code:
  214. stack=4, locals=6, args_size=3
  215. 0: iload_1
  216. 1: iconst_2
  217. 2: iadd
  218. 3: istore_3
  219. 4: ldc #13 // String Hello Wu
  220. 6: astore 4
  221. 8: ldc #14 // float 10.0f
  222. 10: fstore 5
  223. 12: aload_2
  224. 13: iconst_0
  225. 14: ldc2_w #15 // double 10.0d
  226. 17: dastore
  227. 18: return
  228. LineNumberTable:
  229. line 46: 0
  230. line 47: 4
  231. line 48: 8
  232. line 49: 12
  233. line 50: 18
  234. LocalVariableTable:
  235. Start Length Slot Name Signature
  236. 0 19 0 this Lstudies/bytecode/LoadAndStoreTest;
  237. 0 19 1 k I
  238. 0 19 2 arr [D
  239. 4 15 3 m I
  240. 8 11 4 str Ljava/lang/String;
  241. 12 7 5 f F
  242. }
  243. SourceFile: "LoadAndStoreTest.java"

load方法的字节码解读:
image.png
count占两个槽位
image.png
iload_1的意思是将局部变量表中的角标数为1中的num压入操作数栈中,然后操作数栈中的num弹出栈,执行invokevirtual调取pirntln进行打印,然后再执行aload_2将局部变量表中角标数为2的obj压入操作数栈.。。。执行方式跟iload_1一样,以此类推往下执行。
image.png

5、常量入栈指令

常量入栈指令的功能是将常数压入操作数栈,根据数据类型和入栈内容的不同,又可以分为const系列、push系列和ldc指令。

指令const系列:用于对特定的常量入栈,入栈的常量隐含在指令本身里。指令有: iconst(i从-1到5)、lconst(1从0到1)、fconst(f从e到2)、dconst(d从0到1)、aconst_null。注意!!const后面跟的数字跟load不同,这里的是数据值,而load是角标位置的值。
比如:
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指令,使用方式都是类似的。

总结如下:
image.png
例子:
image.png
image.png

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:
image.png
注意两点:
① 局部变量存储的是具体的值,而不是变量名称,这里只是方便演示所以填入的是引用名,对于基本类型存入的是值,引用类型存入的是引用地址值
② 操作数栈是不断的入栈和出栈操作,比如上面的 iadd 是执行 k+2 操作,操作数栈会将 2 和 k 弹出进行相加后将得出的值(m)压入操作数栈,然后再将值(m)取出放入既不变量表中角标为4的位置。

举例2:
image.png
foo方法只有三个槽位,foo方法里有三个变量,但 i 和 s 的作用域在代码块中,所以 i 的槽位被复用了,因为 s 的作用域在代码块中,作用域消失了,所以槽位没有显示,但局部变量表的长度是5,l 占两个槽位
image.png
image.png