JVM(二) — 字节码指令

一、概述

Java虚拟机是基于操作数栈而非寄存器的架构,其操作指令由操作码操作数(0或多个操作数)组成。

  • 操作码:操作码的长度为一个字节(0~255),因此操作码总数不能超过256条。
  • 操作数:一条指令可以有零或者多个操作数,且操作数可以是1个或者多个字节。

关联文章:

二、指令

按照字节码操作用途,大致可以分为如下9类。

  1. 加载和存储指令
  2. 运算指令
  3. 类型转换指令
  4. 对象创建和访问指令
  5. 操作数栈管理指令
  6. 控制转移指令
  7. 方法调用和返回指令
  8. 异常处理指令
  9. 同步指令

具体的详情可以查看文档:《虚拟机规范》

2.1 加载和存储指令

加载和存储指令用于将数据在栈帧中的局部变量和操作数栈之间来回传输。

局部变量操作指令:load & store

load指令: 将局部变量表中指定位置的指定类型变量加载到操作数栈的栈顶。
store指令: 将操作数栈的栈顶相应类型数据保存到局部变量表的指定位置。

进栈操作指令含义出栈操作指令含义
iload第1个int型变量进栈istore栈顶int数值存入第1局部变量
iload_0第1个int型变量进栈istore_0栈顶int数值存入第1局部变量
iload_1第2个int型变量进栈istore_1栈顶int数值存入第2局部变量
iload_2第3个int型变量进栈istore_2栈顶int数值存入第3局部变量
iload_3第4个int型变量进栈istore_3栈顶int数值存入第4局部变量
lload_n第n个long型变量进栈lstore_n栈顶long数值存入第n局部变量
fload_n第n个float型变量进栈fstore_n栈顶float数值存入第n局部变量
dload_n第n个double型变量进栈dstore_n栈顶double数值存入第n局部变量
aload_n第n个ref(引用)型变量进栈astore_n栈顶ref数值存入第n局部变量

常量操作指令:bipushsipushldciconst_<i>

操作指令含义
bipushbyte型常量进栈
sipushshort型常量进栈
aconst_nullnull进栈
iconst_m1int型常量-1进栈
iconst_0int型常量0进栈
iconst_1int型常量1进栈
iconst_2int型常量2进栈
iconst_3int型常量3进栈
iconst_4int型常量4进栈
iconst_5int型常量5进栈
lconst_0long型常量0进栈
fconst_0float型常量0进栈
dconst_0double型常量0进栈
常量池操作含义
ldcint、float或String型常量从常量池推送至栈顶
ldc_wint、float或String型常量从常量池推送至栈顶(宽索引)
ldc2_wlong或double型常量从常量池推送至栈顶(宽索引)

2.2 运算指令

运算或算数指令用于对两个操作数栈上的值进行某种特定运算,并将结果重新存入到操作栈顶。算术指令大体分为两种:对整型数据进行运算、对浮点型数据进行运算。

  • 加法指令: iadd、ladd、fadd、dadd
  • 减法指令: isub、lsub、fsub、dsub
  • 乘法指令: imul、lmul、fmul、dmul
  • 除法指令: idiv、ldiv、fdiv、ddiv
  • 求余指令: irem、lrem、frem、drem
  • 取反指令: ineg、lneg、fneg、dneg
  • 位移指令:ishl、ishr、iushr、lshl、lshr、lushr
  • 按位或指令: ior、lor
  • 按位与指令:: iand、land
  • 按位异或指令:: ixor、lxor
  • 局部变量表自增指令:iin
  • 比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp

2.3 类型转换指令

类型转换指令可以将两种不同的数值类型进行相互转换,这些指令操作一般用于实现用户代码中显示类型转换操作。

宽化类型转换: 虚拟机直接支持如下类型的宽化类型转换。

  • int 类型转 long、float、double类型。
  • long 类型转 float、double类型。
  • float 类型转 double类型。

窄化类型转换: 必须显式地调用类型转换指令来完成转换,并且该过程很可能导致精度丢失。

  • 转换指令:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l、d2f。

2.4 对象创建和访问指令

  • 创建类实例的指令: new
  • 创建数组的指令:newarray、anewarray、multianewarray
  • 访问类字段(static字段)和实例字段(非static字段)的指令:getfield、putfield、getstatic、putstatic
  • 将一个数组元素加载到操作数栈的指令:xaload (x可为b,c,s,i,l,f,d,a)
  • 将一个操作数栈的值存储到数组元素的指令: xastore (x可为b,c,s,i,l,f,d,a)
  • 获取数组长度的指令:arraylength
  • 检查类实例类型的指令:instanceof、checkcast

2.5 操作数栈管理指令

直接操作操作数栈的指令:

  • 将操作数栈的栈顶一个或两个元素出栈:pop、pop2。
  • 赋制栈顶一个或两个数值并将赋值的值重新亚茹栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2。
  • 将栈最顶端的两个数值互换:swap。

如果对dup相关指令由疑问可以直接查看《虚拟机规范》 中的dup指令(如下图所示)。下图 Operand Stack 代表了操作数栈中dup2指令操作前后的变化。
Form1:
如果操作数栈有两个相同类型的数据时,当执行 dup2 指令时,会赋制栈顶两个元素,最终操作数栈的数据变为:…, value2, value1 —> …, value2, value1, value2, value1
Form2:
如果操作数栈栈顶两个数据类型不同时,当执行 dup2 指令时,只会赋制1个栈顶元素,最终操作数栈的数据变为:…, value1 —> …, value1, value1

在这里插入图片描述

2.6 控制转移指令

控制指令是指可以让Java虚拟机有条件或无条件地从指定位置指令继续执行。

  • 条件分支:ifeq、iflt、ifnull、ifnonnull、if_icmpeq 等
  • 复合分支:tableswitch、lookupswitch
  • 无条件分支:goto、goto_w、jsr、jsr_w、ret

2.7 方法调用和返回指令

方法调用

方法调用作用解释
invokevirtual调用实例方法虚方法分派
invokestatic调用类方法static方法
invokeinterface调用接口方法运行时搜索合适方法调用
invokespecial调用特殊实例方法包括实例初始化方法、父类方法
invokedynamic由用户引导方法决定运行时动态解析出调用点限定符所引用方法

方法返回

方法返回含义
return当前方法返回void
ireturn当前方法返回int
lreturn当前方法返回long
freturn当前方法返回float
dreturn当前方法返回double
areturn当前方法返回ref

2.8 异常处理指令

Java 程序中显式抛出异常的操作都是由 athrow 指令来实现。异常的处理(catch)不是由字节码指令来实现,而是采用异常表来完成。

2.9 同步指令

方法级的同步和方法内部分代码的同步,都是使用管程(Monitor)来实现的。

同步一段指令集序列,在Java语言中使用 synchronized 语句块来实现,在Java虚拟机层面通过指令集中的 monitorentermonitorexit 两条指令来完成 synchronized 的功能。为了保证monitorenter和monitorexit指令一定能成对的调用(不管方法正常结束还是异常结束),编译器会自动生成一个异常处理器,该异常处理器的主要目的是用于执行monitorexit 指令。

参考资料

  1. 《深入理解Java虚拟机》
  2. Java 虚拟机规范:https://docs.oracle.com/javase/specs/jvms/se8/html/
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值