字节码指令

Java虚拟机采用的是面向操作数栈而不是面向寄存器的架构,所以大多数指令只有操作数码都不包含操作数栈,只有一个操作数码,指令参数都存放在操作数栈中。 字节码指令集是优势和劣势都很突出的指令集架构,由于限制了Java虚拟机操作码长度只有1个字节(0-255),所以操作码总数不能超过256. Class文件格式放弃了编译后代码的操作数长度对齐,这就意味着虚拟机在处理超过一个字节的数据时,不得不在运行时从字节中重建出具体的数据结构(比如要把一个 16位无符号整数使用两个无符号字节存储起来,假设命令为byte1和byte2,那么存储应该是byte1<<8|byte2),这种模型会损失一些执行上的性能,优势在于 放弃了操作数长度对齐填充,意味着可以省了大量的填充和符号间隔。

# 字节码与数据类型

iload指令从局部变量表中加载int类型的数据到操作数栈中,i就是int类型的助记符。如果每一种数据类型都有对应的操作码,对操作码容量上限(256)会造成很大的压力, 所以虚拟机规范规定并非每种数据类型和每一种操作都有对应的指令。大部分指令都没有支持整数类型byte、char、short甚至没有任何指令支持boolean类型,编译器会在编译期或运行期 将byte和short类型的数据带符号扩展为相应的int类型数据。

# 加载和存储指令

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

  1. iload_ 将一个局部变量加载到操作数栈
  2. istore_ 将一个数值从操作数栈存储到局部变量表

# 运算指令

算数指令用于对两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶。虚拟机规范并没有规定整型类型数据溢出具体会得到什么计算结果,仅规定在求余或除法时,当除数为0,虚拟机会抛出 ArithmeticException,其他任何整型数据运算场景都不应该抛出运行时异常。

# 类型转换指令

可以将两种不同数值类型的数据相互转换。分为宽化类型转换和窄化类型转换(需要编写程序时显式声明,一般是大范围数据类型向小范围数据类型)。

# 对象创建和访问指令

虽然类实例和数组都是对象,但是Java虚拟机对类实例和数组的创建和操作使用了不同的字节码指令。

# 操作数栈管理指令

直接操作数栈指令包含,栈顶元素出栈pop,复制栈顶元素重新压入栈顶dup2,互换栈顶元素swap等

# 控制转移指令

控制转移指令可以让Java虚拟机有条件或无条件从指定位置指令的下一条指令继续执行程序。

# 方法调用和返回指令

方法调用指令与数据类型无关,方法的返回执行是根据返回值的类型区分的,包含(ireturn返回boolean short byte char和int)

# 异常处理指令

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

# 同步指令

Java虚拟机可以支持方法级的同步和方法内存一段指令序列的同步。这两种同步结构都是上使用管程(Monitor)来实现的。
  方法级别的同步是隐式的,无须通过字节码指令来控制,它实现在方法调用和返回操作之中。虚拟机可以从方法常量池中的方法表结构中ACC_SYNCHRONIZED访问标志得知一个方法 是否被声明为同步方法。monitor无论方法是否正常执行完毕,都会自动释放。
  同步一段指令集序列通常是由Java语言中的synchronized语句块来表示的。Java虚拟机指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字。 正确实现synchronized关键字需要javac编译器和java虚拟机协作支持。