JVM 字节码指令
字节码指令属于方法表中的内容。Java 虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)而构成。由于限制了 Java 虚拟机操作码的长度为一个字节(即0~255),这意味着指令集的操作码总数不可能超过 256 条。
- 大多数的指令都包含了其操作所对应的数据类型信息。例如:
iload
指令用于从局部变量表中加载int
型的数据到操作数栈中,而fload
指令加载的则是float
类型的数据。 - 大多数对于
boolean, byte, short, char
类型数据的操作,实际上都是使用相应的int
类型作为运算类型
常见指令
指令 | 常用指令 | 说明 |
---|---|---|
加载和存储指令 |
iload istore bipush iconst_<i>
|
用于将数据在栈帧中的局部变量表和操作数栈之间来回传输 加载和存储指令 |
运算或算术指令 |
iadd isub imul
|
用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶 运算或算术指令 |
类型转换指令 | i2b |
可以将两种不同的数值类型进行窄化类型转换 类型转换指令 |
创建类实例的指令 | new |
|
创建数组的指令 |
newarray anewarray multianewarray
|
|
访问字段指令 |
getfield putfield getstatic putstatic
|
|
数组存取相关指令 |
iaload iastore arraylength
|
数组存取相关指令 |
检查类实例类型的指令 |
instanceof checkcast
|
|
操作数栈管理指令 |
pop dup swap
|
直接操作操作数栈的指令 操作数栈管理指令 |
控制转移指令 |
ifeq tableswitch goto
|
从指定的位置继续执行程序 控制转移指令 |
方法调用指令 |
invokevirtual invokeinterface invokespecial invokestatic invokedynamic invokedynamic
|
方法调用指令与数据类型无关 方法调用指令 |
方法返回指令 | ireturn |
方法返回指令 |
异常处理指令 | athrow |
显式抛出异常的操作(throw 语句)都由 athrow 指令来实现的 |
同步指令 |
monitorenter monitorexit
|
synchronized |
加载和存储指令
-
将一个局部变量加载到操作栈:
iload, iload_<n>, lload, lload_<n>, fload, fload_<n>, dload, dload_<n>, aload, aload_<n>
-
将一个数值从操作数栈存储到局部变量表:
istore, istore_<n>, lstore, lstore_<n>, fstore,fstore_<n>, dstore, dstore_<n>, astore, astore_<n>
-
将一个常量加载到操作数栈:
bipush, sipush, ldc, ldc_w, ldc2_w, aconst_null, iconst_m1, iconst_<i>, lconst_<l>, fconst_<f>, dconst_<d>
-
扩充局部变量表的访问索引的指令:
wide
运算或算术指令
- 加法指令:
iadd, ladd, fadd, dadd
- 减法指令:
isub, lsub, fsub, dsub
- 乘法指令:
imul, lmul, fmul, dmul
类型转换指令
JVM 直接支持以下数值类型的宽化类型转换(即小范围类型向大范围类型的安全转换):
-
int
➡long, float, double
-
long
➡float, double
-
float
➡double
处理窄化类型转换(Narrowing Numeric Conversions)时,必须显式地使用转换指令来完成。
这些转换指令包括:i2b, i2c, i2s, l2i, f2i, f2l, d2i, d2l, d2f
。
数组存取相关指令
-
把一个数组元素加载到操作数栈的指令:
baload, caload, saload, iaload, laload, faload, daload, aaload
-
将一个操作数栈的值存储到数组元素中的指令:
bastore, castore, sastore, iastore, fastore, dastore, aastore
-
取数组长度的指令:
arraylength
操作数栈管理指令
-
将操作数栈的栈顶一个或两个元素出栈:
pop, pop2
-
复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:
dup, dup2, dup_x1, dup2_x1, dup_x2, dup2_x2
-
将栈最顶端的两个数值互换:
swap
控制转移指令
控制转移指令可以让 JVM 有条件或无条件地从指定的位置指令而不是控制转移指令的下一条指令继续执行程序,从概念模型上理解,可以认为控制转移指令就是在有条件或无条件地修改PC 寄存器的值。
-
条件分支:
ifeq, iflt, ifle, ifne, ifgt, ifge, ifnull, ifnonnull, if_icmpeq, if_icmpne, if_icmplt, if_icmpgt, if_icmple, if_icmpge, if_acmpeq, if_acmpne
-
复合条件分支:
tableswitch, lookupswitch
-
无条件分支:
goto, goto_w, jsr, jsr_w, ret
方法调用指令
-
invokevirtual
指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),这也是 Java 语言中最常见的方法分派方式。 -
invokeinterface
指令用于调用接口方法,它会在运行时搜索一个实现了这个接口方法的对象,找出适合的方法进行调用。 -
invokespecial
指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法、私有方法和父类方法。 -
invokestatic
指令用于调用类方法(static 方法)。 -
invokedynamic
指令用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法,前面 4 条调用指令的分派逻辑都固化在 Java 虚拟机内部,而invokedynamic
指令的分派逻辑是由用户所设定的引导方法决定的。比如 Lambda 表达式。
方法返回指令
根据返回值的类型区分,包括:
-
ireturn
当返回值是boolean, byte, char, short, int
类型时使用 -
lreturn, freturn, dreturn, areturn
-
return
指令供声明为void
的方法、实例初始化方法以及类和接口的类初始化方法使用
Exception table 异常表
只要在 from
和 to
之间发生了异常,就会跳转到 target
所指定的位置
-
from
指定字节码索引的开始位置 -
to
指定字节码索引的结束位置 -
targe
异常处理的起始位置 -
type
异常类型
Finally
Java 编译器使用了一种比较傻的方式来组织 finally
的字节码,它分别在 try、catch 的正常执行路径上,复制一份 finally
代码,追加在正常执行逻辑的后面;同时,再复制一份到其他异常执行逻辑的出口处。
装箱拆箱
Java 中有 8 种基本类型,但鉴于 Java 面向对象的特点,它们同样有着对应的 8 个包装类型,比如 int
和 Integer
,包装类型的值可以为 null
(基本类型没有 null 值,而数据库的表中普遍存在null 值,所以实体类中所有属性均应采用封装类型),很多时候,它们都能够相互赋值。
public Integer cal () {
Integer a = 1000; // 字节码使用的是 Integer.valueOf 方法
int b = a* 10; // 字节码调用了 Integer.intValue 方法来获取基本类型的值
return b; // 字节码使用了 Integer.valueOf 方法对结果进行了包装
}
IntegerCache
会把 -128 ~ 127 的值进行缓存,用到的时候直接从缓存中拿,可以使用-XX:AutoBoxCacheMax
修改缓存的上限。
foreach
- 数组遍历被编译为
for(int i;i<length;i++)
-
List
遍历被编译为Iterator
的方式
注解
-
RuntimeInvisibleAnnotations
类注解,方法注解 -
RuntimeInvisibleParameterAnotations
参数注解
示例——数组操作
数组创建
int[] arr = new int[]{1111, 2222,3333,4444}
1: newarray int // 创建 int 数组
3: dup // 复制数组的引用压入栈顶
4: iconst_0 // 数组下标,常量 0 加载到操作数栈
5: sipush 1111 // 将常量 1111 加载到操作数栈
8: iastore // 数组对应下标位置赋值
数组访问
int a = arr[2]
28: aload_1 // 将第二个引用类型本地变量推送至栈顶,这里是生成的数组
29: iconst_2 // 数组下标,将 int 型 2 推送至栈顶
30: iaload // 将 int 型数组指定索引的值推送至栈顶
上一篇: JVM 内存结构
下一篇: 【JVM性能调优】JVM内存结构