# 字节码指令
# 局部变量压栈指令
局部变量压栈指令是将给定的局部变量表中的数据压入操作数栈
这类指令大体可以分为 :
x_load_<n>(x 为 i, l, f, d, a n 为 0, 1, 2, 3)- xload (x 为 i, l, f, d, a)
指令 xload_n 标识将第 n 个局部变量压入操作数栈,比如 iload_1, fload_0, aload_0 等指令。其中 aload_n 标识将一个对象引用压栈
指令 xload 通过指定参数的形式,把局部变量压入操作数栈,当使用这个命令时,表示局部变量的数量可能超过了 4 个。比如 iload, fload 等
# 常量入栈指令
常量入栈指令的功能是将常熟压入操作数栈,根据数据类型和入栈内容的不同,又可以分为 const 系列,push 系列和 ldc 指令
指令 const 系列: 用于对特定的常量入栈,入站的常量隐含在指令本身里。指令有 :
iconst_<i>(i 从 - 1 到 5),l_const_<l>(l 从 0 到 1),fconst_<f>(f 从 0 到 2),dconst_<d>(d 从 0 到 1), aconst_null比如 : 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 表示对象引用。如果指令隐含操作的参数,会以下划线形式给出
指令 push 系列: 主要包括 bipush 和 sipush. 它们的区别在于接收数据类型的不同. bipush 接受 8 位整数作为参数,sipush 接收 16 位整数,它们都将参数压入栈
指令 ldc 系列: 如果以上指令都不能满足需求,那么可以使用万能的 ldc 指令,它可以接收一个 8 位的参数,该参数指向常量池中的 int, float 或者 string 的索引,将指定的内容压入堆栈
类似的还有 ldc_w, 它接收两个 8 位参数,能支持的索引范围大于 ldc
如果要押入的元素是 long 或者 double 类型的,则使用 ldc2_w 指令,使用方式都是类似的
# 出栈装入局部变量表指令
出栈装入局部变量指令用于将操作数栈中栈顶元素弹出后,装入局部变量表的指定位置,用于给局部变量赋值.
这类指令主要以 store 的形式存在,比如 xstore (x 为 i, l, f, d, a), xstore_n (x 为 i, l, f, d, a n 为 0, 1, 2, 3)
- 其中,指令 istore_n 是将从操作数栈中弹出一个整数,并把它赋值给局部变量索引 n 位置
- 指令 xstore 由于没有隐含参数信息,故需要提供一个 byte 类型的参数制定目标局部变量表的位置
说明 :
一般来说,类似像 store 这样的命令需要带一个参数,用来指明将弹出的元素放在局部变量表的第几个位置. 但是,为了尽可能压缩指令大小,使用专门的 istore_1 指令表示将要弹出的元素放置在局部变量表第 1 个位置。类似的还有 istore_0, istore_2, istore_3, 他们分别表示从操作数栈顶弹出一个元素,存放在局部变量表中第 0, 2, 3 个位置
由于局部变量表前几个位置总是非常常用,因此这种做法虽然增加了指令数量,但是可以大大压缩生成的字节码的体积。如果局部变量表很大,需要存储的槽位大于 3, 那么可以使用 istore 指令,外加一个参数,用来表示需要存放的槽位位置
# 算术指令
算术指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新压入操作数栈
分类 :
大体上算术运算符可以分为两种:对整形数据进行运算的指令与对浮点类型数据进行运算的指令
Java 虚拟机中的实际类型与运算类型
| 实际类型 | 运算类型 | 分类 |
|---|---|---|
| boolean | int | 1 |
| byte | int | 1 |
| char | int | 1 |
| short | int | 1 |
| int | int | 1 |
| float | float | 1 |
| reference | reference | 1 |
| returnAddress | returnAddress | 1 |
| long | long | 2 |
| double | double | 2 |
运算时的溢出
数据运算可能会导致溢出,例如两个很大的正整数相加,结果可能是一个负数。起始 Java 虚拟机规范并无明确规定过整型数据溢出的具体结果,仅规定了在处理整型数据时,只有除法指令以及求余指令中当出现除数为 0 会导致虚拟机抛出异常 ArithmeticException
运算模式
- 向最接近数舍入模式 : JVM 要求在进行浮点数计算时,所有的运算结果都必须舍入到适当的精度,非精确结果必须舍入为可被表示的最接近的精确值。如果有两种可表示的形式与该值一样接近,将优先选择最低有效位为零的
- 向零舍入模式:将浮点数转换为整数时,采用该模式,该模式将在目标数值类型中选择一个最接近但是不大于原值的数字作为最精确的舍入结果
NaN 值的使用
当一个操作产生溢出时,将会使用有符号的无穷大表示。如果某个操作结果没有明确的数学定义的话,将会使用 NaN 值来表示。而且所有使用 NaN 值作为操作数的算术操作,结果都会返回 NaN
