概述

  • Java字节码对于虚拟机,就好像汇编语言对于计算机,属于基本执行指令。
  • Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)而构成。由于Java虚拟机采用面向操作数栈而不是寄存器的结构,所以大多数的指令都不包含操作数,只有一个操作码。
  • 由于限制了Java 虚拟机操作码的长度为一个字节(即0~255),这意味着指令集的操作码总数不可能超过256条。
  • 官方文档: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html
  • 熟悉虚拟机的指令对于动态字节码生成、反编译Class文件、Class文件修补都有着非常重要的价值。因此,阅读字节码作为了解Java 虚拟机的基础技能,需要熟练掌握常见指令。

执行模型

如果不考虑异常处理的话,那么Java虚拟机的解释器可以使用下面这个伪代码当做最基本的执行模型来理解

do{
    自动计算PC寄存器的值加1;
    根据PC寄存器的指示位置,从字节码流中取出操作码;
    if(字节码存在操作数)从字节码流中取出操作数;
    执行操作码所定义的操作;
}while(字节码长度>0);

字节码与数据类型

在Java虚拟机的指令集中,大多数的指令都包含了其操作所对应的数据类型信息。例如,iload指令用于从局部变量表中加载 int 型的数据到操作数栈中,而 fload 指令加载的则是float类型的数据。

对于大部分与数据类型相关的字节码指令**,它们的操作码助记符中都有特殊的字符来表明专门为哪种数据类型服务**:

  • i代表对int类型的数据操作,
  • l代表long
  • s代表short
  • b代表byte|
  • c代表char
  • f代表float
  • d代表double

也有一些指令的助记符中没有明确地指明操作类型的字母,如 arraylength 指令,它没有代表数据类型的特殊字符,但操作数永远只能是一个数组类型的对象。

还有另外一些指令,如无条件跳转指令 goto 则是与数据类型无关的。

大部分的指令都没有支持整数类型 byte、char 和 short,甚至没有任何指令支持 boolean 类型。编译器会在编译期或运行期将 byte 和 short 类型的数据带符号扩展(Sign-Extend) 为相应的int类型数据,将 boolean 和 char 类型数据零位扩展(Zero-Extend) 为相应的 int 类型数据。 与之类似,在处理 boolean、 byte、short 和 char 类型的数组时,也会转换为使用对应的 int 类型的字节码指令来处理。因此,大多数对于 boolean、 byte、 short 和 char 类型数据的操作,实际上都是使用相应的int类型作为运算类型。

指令分析

由于完全介绍和学习这些指令需要花费大量时间。为了让大家能够更快地熟悉和了解这些基本指令,这里将JVM中的字节码指令集按用途大致分成9类。

  • 加载与存储指令
  • 算术指令
  • 类型转换指令
  • 对象的创建与访问指令
  • 方法调用与返回指令
  • 操作数栈管理指令
  • 比较控制指令
  • 异常处理指令
  • 同步控制指令

在做值相关操作时:

  • 一个指令,可以从局部变量表、常量池、堆中对象、方法调用、系统调用中等取得数据,这些数据(可能是值,可能是对象的引用)被压入操作数栈。
  • 一个指令,也可以从操作数栈中取出一到多个值(pop多次),完成赋值、加减乘除、方法传参、系统调用等等操作。