本文主要介绍Java虚拟机的字节码相关的内容. 由于Java字节码的独特设计, 使得大部分指令仅需要一个字节即可表示, 这也是其被称为字节码的原因.
由于Java字节码严格区分数据类型, 因此很多指令都会针对不同的数据类型单独设计一条指令. 例如load指令根据数据类型的不同, 就存在iload和fload等特殊指令.
但如果每一条指令都针对各种数据类型设计一种字节码, 那么字节码的范围可能也无法容纳. 因此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虚拟机规划规定, 除了
div和rem指令遇到除数为零的情况抛出异常以外, 其他任何整数运算都不会抛出算术异常
类型转换指令
| 操作 | 字节码格式 |
|---|---|
| 类型转换 | <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 |
pop和pop2分别表示将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的同步分为方法级同步和代码段同步. 方法级同步是隐式实现的, 蕴含在方法调用和返回的字节码中. 而代码段级别的同步是显式的, 使用monitorenter和monitorexit实现.
扩展资料
最后更新: 2025年12月26日 23:04
版权声明:本文为原创文章,转载请注明出处