本文主要介绍Java虚拟机的字节码相关的内容. 由于Java字节码的独特设计, 使得大部分指令仅需要一个字节即可表示, 这也是其被称为字节码的原因.

由于Java字节码严格区分数据类型, 因此很多指令都会针对不同的数据类型单独设计一条指令. 例如load指令根据数据类型的不同, 就存在iloadfload等特殊指令.

但如果每一条指令都针对各种数据类型设计一种字节码, 那么字节码的范围可能也无法容纳. 因此JVM引入了一些不平衡的设计, 对于部分指令, 并没有专用的字节码, 而是与其他类型共用一种字节码.

下面将字节码分为不同的大类, 分别介绍其含义.

加载与存储指令

字节码 含义
<T>load , <T>load_<n> 将局部变量加载到操作栈
<T>store, <T>store_<n> 将操作栈上的数据存储到局部变量
<T>const_<I> 将指定类型的常量I加载到操作栈
bipush, sipush 将byte类型/short类型的指定常量推送到操作栈顶

补充说明

  • iload表示将一个指定的int类型数据加载到操作栈, 而fload_2表示将第二个本地的float变量加载到操作栈.
  • iconst_1表示把int类型的常量1加载到操作栈, 而dconst_2表示把double类型常量2.0加载到操作栈.
  • bipush指令后面需要加上需要推送的数字

ldc, ldc_w, ldc2_w, aconst_null, iconst_m1, wide

运算指令

运算指令与汇编的命名基本相同, 具体如下

操作 字节码格式
算术操作 <T>add, <T>sub, <T>mul, <T>div, <T>rem, <T>neg, <T>inc
移位操作 <T>[u]shl<l/r>
逻辑操作 <T>or, <T>and, <T>xor
比较操作 <f/d>cmp<g/l> lcmp
  • 所有的运算操作的结果都会自动压入操作栈(虽然这好像是废话)
  • 移位操作可选有符号移位和无符号移位, 左移和右移, 例如iushl表示int型无符号左移
  • 比较操作先比较栈顶两个数字的大小, 并将比较结果(-1, 0, 1)入栈
  • 浮点类型的比较操作可以对NaN进行特殊处理, 例如fcmpg表示存在NaN时将1入栈, 而dcmpl表示存在NaN时将-1入栈

Java虚拟机规划规定, 除了divrem指令遇到除数为零的情况抛出异常以外, 其他任何整数运算都不会抛出算术异常

类型转换指令

操作 字节码格式
类型转换 <T>2<U>
  • long类型转化为int类型的指令为l2i. 将double类型转化为int类型的指令为d2i
  • 整数转换直接丢弃高位数据, 这一操作可能导致符号变化
  • 浮点类型转整数类型T遵循如下的规则
    • 如果浮点数为NaN, 则相应的整数为0, 否则先按照规则舍入成整数V
    • 如果V处于T的表示范围, 则转换值为V
    • 否则根据V的符号转化为T能表达的最大值或最小值
  • double类型数据转为float类型数据, 按照规划处理
    • 将double类型数据舍入为最接近的float类型数据
    • 如果double类型数据绝对值太小, 返回float类型的正负零
    • 如果doubel类型数据绝对值太大, 返回float类型的NaN
    • 如果double类型数据为NaN, 返回float类型的NaN

对象创建与访问指令

操作 字节码格式
创建对象 new
创建数组 newarray, anewarray, multianewarray
访问对象 getfield, putfield, getstatic, putstatic
访问数组 <T>aload, <T>astore, arraylength
检查实例 instanceof, checkcast

操作数栈管理指令

操作 字节码格式
出栈 pop, pop2
复制 dup, dup2, dup_x<I>, dup2_x<I>
交换 swap
  • poppop2分别表示将1个/2个元素出栈
  • swap表示交换栈顶的两个元素

dup系列的指令比较复杂, 其复制操作涉及到栈顶数据的长度. dup2既可以复制栈顶的一个2倍长度的数据, 也可以复制两个1倍长度的数据.

控制转移指令

控制转移指令与汇编类似, 不过转移条件都是根据栈顶元素确定, 包括如下的指令

操作 字节码格式
条件跳转 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
  • 条件跳转指令比较栈顶int值与0的大小, 并决定是否跳转
  • 比较跳转指令比较栈顶两个int值的大小, 并确定是否跳转
  • 对于小于int范围的数据, 使用int类型的比较跳转指令
  • 对于浮点类型, 先执行比较指令, 然后使用条件跳转指令进行跳转

方法调用和返回指令

操作 字节码格式
方法调用 invoke<virtual / interface / special / static/ dynamic>
返回 <T>return
  • 前四种方法调用指令分别调用对象的实例方法, 接口方法, 特殊方法(例如初始化方法)和静态方法
  • 第五中方法调用指令调用运行时动态解析的方法
  • 调用函数不区分类型, 返回时指定返回值类型

异常指令

使用athrow指令抛出栈顶的异常对象. 此外JVM内部的一些操作也会抛出异常, 例如除法的除数为零.

同步指令

Java的同步分为方法级同步和代码段同步. 方法级同步是隐式实现的, 蕴含在方法调用和返回的字节码中. 而代码段级别的同步是显式的, 使用monitorentermonitorexit实现.

扩展资料

最后更新: 2024年04月24日 15:50

版权声明:本文为原创文章,转载请注明出处

原始链接: https://lizec.top/2021/01/11/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3JVM%E4%B9%8B%E5%AD%97%E8%8A%82%E7%A0%81/