字节码指令解析上篇
一、加载与存储指令
1.作用
加载和存储指令,用于将数据从栈帧的局部变量表和操作数栈之间来回传递。
2.常用指令
- 局部变量入栈指令:将一个局部变量加载到操作数栈,比如iload、fload、iload_
- 常量入栈指令:将一个常量加载到操作数栈,比如bipush、sipush、ldc、ldc_w、1dc2_W、aconst_null、iconst_m1、iconst_*、lconst_、fconst_、dconst_
- 出栈指令:将一个数值从操作数栈存储到局部变量表,比如xstore、xstore_、xastore
- 扩充局部变量表的访问索引的指令::wide
💡【注意】:上面出现的iload_指令代表了iload_0、iload_1、iload_2和iload_3这几个指令,这几组指令都是某个带有一个操作数的通用指令(例如 iload)的特殊形式,对于这若干组特殊指令来说,它们表面上没有操作数,不需要进行取操作数的动作,但操作数都隐含在指令中。
3.操作数栈
-
Java字节码是Java虚拟机所使用的指令集,因此它与Java虚拟机基于栈的计算模型是密不可分的。在解释执行过程中,每当为Java方法分配栈桢时,Java虚拟机往往需要开辟一块额外的空间作为操作数栈,来存放计算的操作数以及返回结果。
-
在执行指令之前,Java虚拟机要求该指令的操作数已被压入操作数栈中。在执行指令时,Java虚拟机会将该指令所需的操作数弹出,并且将指令的结果重新压入栈中。
举例:完成1 + 2的算术运算
这里使用的是加法指令iadd,在执行该指令前,栈顶的两个元素分别为整数1和整数2,那么执行iadd指令时会将这两个整数弹出栈,并将求得的和整数3压入栈。
4.局部变量表
- Java方法栈桢的另外一个重要组成部分则是局部变量表,字节码程序可以将计算的结果缓存在局部变量表中。实际上,Java虚拟机将局部变量表作为一个数组,依次存放方法中所涉及的所有局部变量。
- 在栈帧中,与性能调优关系最为密切的部分就是局部变量表,而且局部变量表中的变量也是重要的垃圾回收根节点,因为只要被局部变量表中直接或间接引用的对象都不会被回收。
举例
public void foo(long l, float f){
{
int i = 0;
}
{
String s = "Hello, World";
}
}
5.压栈指令
- 局部变量压栈指令是将给定的局部变量表中的数据压入到操作数栈中。
- 指令形式一般由:
xload_
(x为i、l、f、d、a,n为日到3)、xload
(x为i、l、f、d、a),指令xload_n表示将第n个局部变量压入操作数栈,比如iload_1、fload_0、aload_0等指令。
代码举例
6.入栈指令
-
常量入栈指令的功能是将常数压入操作数栈,根据数据类型和入栈内容的不同,又可以分为
const
系列指令、push
系列指令和ldc
系列指令。 -
const系列
用于对特定的常量入栈,入栈的常量隐含在指令本身里,指令有: iconst_(i从-1到5)、lconst_(l从0到1)、fconst_(f从0到2)、dconst_(d从0到1)、aconst_null 。
-
push系列
指令有:bipush、sipush,它们的区别在于接收数据类型的不同,bipush接收8位整数作为参数,sipush接收16位整数。
-
ldc系列
ldc指令,它可以接收一个8位的参数,该参数指向常量池中的int、float或者String的索引,将指定的内容压入堆栈。类似的还有
ldc_w
,它接收两个8位参数。另外如果要压入的元素是long、double类型的,则使用ldc2_w
指令。
数据类型与指令的对应关系
代码举例
7.出栈指令
- 出栈装入局部变量表的指令用于将操作数栈中栈顶元素弹出后,装入局部变量表的指定位置,然后给局部变量赋值。
- 这类指令主要以store的形式存在,比如:
xstore、xstore_n
。- 指令istore_n将从操作数栈中弹出一个整数,并把它赋值给局部变量索引n位置。
- 指令xstore由于没有隐含参数信息,故需要提供一个byte类型的参数类指定目标局部变量表的位置。
- 我们看到
xstore_n
这样的命令需要带一个参数,用来指明将弹出的元素放在局部变量表的第几个位置。但是,为了尽可能压缩指令大小,使用专门的istore_1
指令表示将弹出的元素放置在局部变量表第1个位置,类似的还有istore_0、istore_2、istore_3
,它们分别表示从操作数栈顶弹出一个元素,存放在局部变量表第0、2、3个位置。由于局部变量表前几个位置总是非常常用,因此这种做法虽然增加了指令数量,但是可以大大压缩生成的字节码的体积。如果局部变量表很大,需要存储的槽位大于3,那么可以使用istore
指令,外加一个参数,用来表示需要存放的槽位位置。
代码举例
二、算术指令
1.基本概述
- 算术指令用于对两个操作数栈上的值进行某种特定运算,并把计算结果重新压入操作数栈。
- 大体上算术指令可以分为两种:对整型数据进行运算的指令和对浮点类型数据进行运算的指令。
2.实际类型和运算类型对应关系
3.指令分类
-
加法指令:iadd、ladd、fadd、dadd
-
减法指令:isub、lsub、fsub、dsub
-
乘法指令:imu、lmu、fmul、dmul
-
除法指令:idiv、ldiv、fdiv、ddiv
-
求余指令:irem、lrem、frem、drem
-
取反指令:ineg、lneg、fneg、dneg
-
自增指令:iinc
-
位运算指令:
- 位移指令:ishl、ishr、 iushr、lshl、lshr、 lushr
- 按位或指令:ior、lor
- 按位与指令:iand、land
- 按位异或指令:ixor、lxor
- <