本文主要介绍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
实现.
扩展资料
最后更新: 2024年10月19日 22:36
版权声明:本文为原创文章,转载请注明出处