欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JVM 字节码指令

程序员文章站 2022-04-18 15:51:52
...

字节码指令属于方法表中的内容。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 直接支持以下数值类型的宽化类型转换(即小范围类型向大范围类型的安全转换):

  • intlong, float, double
  • longfloat, double
  • floatdouble

处理窄化类型转换(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 异常表

只要在 fromto 之间发生了异常,就会跳转到 target 所指定的位置

  • from指定字节码索引的开始位置
  • to指定字节码索引的结束位置
  • targe异常处理的起始位置
  • type异常类型

Finally

Java 编译器使用了一种比较傻的方式来组织 finally 的字节码,它分别在 try、catch 的正常执行路径上,复制一份 finally 代码,追加在正常执行逻辑的后面;同时,再复制一份到其他异常执行逻辑的出口处。

装箱拆箱

Java 中有 8 种基本类型,但鉴于 Java 面向对象的特点,它们同样有着对应的 8 个包装类型,比如 intInteger,包装类型的值可以为 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