JVM 指令主要包含了一下几种类型:加载和存储指令、运算指令、类型转换指令、对象创建与访问指令、操作数栈管理指令、控制转移指令、方法调用和返回指令、异常处理指令、同步指令等。

基于栈的解释器执行过程

下面看一下一个简单的代码片段,如下所示:

public class StackTest { public int calc() { int a = 100; int b = 200; int c = 300; return (a b) * c; } }

通过 jclasslib 工具或者 javap -verbose 命令,可以得到 calc() 方法的字节码指令。如下所示:

0 bipush 100 2 istore_1 3 sipush 200 6 istore_2 7 sipush 300 10 istore_3 11 iload_1 12 iload_2 13 iadd 14 iload_3 15 imul 16 ireturn

下面来具体的说明一下整个方法的执行过程:

程序中中文对应的字节码(字节码指令解释执行)(1)

程序中中文对应的字节码(字节码指令解释执行)(2)

程序中中文对应的字节码(字节码指令解释执行)(3)

程序中中文对应的字节码(字节码指令解释执行)(4)

程序中中文对应的字节码(字节码指令解释执行)(5)

程序中中文对应的字节码(字节码指令解释执行)(6)

程序中中文对应的字节码(字节码指令解释执行)(7)

上面的指令执行过程只是一个概念模型,JVM 会对过程做一些优化来提高性能,JVM 在实际运行时可能执行过程差距比较大,并且不同虚拟机的执行也不尽相同。

加载和存储指令

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

运算指令

运算指令作用于操作数栈上面的2个值的特定运算,并且把结果重新存入操作数栈顶。大体上可以分为2类:对整型、浮点型数值运算。因为 JVM 指令集中没有byte、short、char和boolean 类型的算术运算,所以都使用了对应的 int 类型的指令代替。

类型转换指令

类型转换指令可以将2种不同类型的数值相互转换,这些转换一般实现于代码中的显示类型转换,主要有以下类型:

对于显示的类型转换,一般情况下都是窄化类型转换(也就是丢失精度的转化,如:long转为int等)。常见的转换指令有:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l、d2f等。

对象创建与访问指令

对于普通对象和数组的创建,JVM分别使用了不同的指令去处理。

操作数栈管理指令

如同一个普通的堆栈一样,JVM 提供了直接操作操作数栈的指令。

控制转移指令

控制转移指令可以让 JVM,跳转到指定的偏移地址的字节码执行。从上面的模型图看来,就是修改程序计数器的值。

方法调用和返回指令

方法调用包含了以下指令。

上述的前4条指令都是固化在 JVM 内部的,invokedynamic 的分派逻辑是由用户所设定的引导方法决定的。

方法的调用指令与数据类型无关,而方法的返回指令是根据返回值区分的。包括:ireturn(当返回值是boolean、byte、char、short、int)、lreturn、freturn、dreturn和areturn。return指令提供给:返回值为void的指令、实例方法初始化、接口类方法初始化。

异常处理指令

Java程序中显示抛出异常的操作都是由athrow指令实现的。

同步指令

JVM 可以支持方法级的同步和方法内的同步,这两种同步结构都是由管程(Monitor)来实现的。

方法级的同步是隐式的,无需通过字节码指令来控制。JVM 可以从方法常量池的方法表结构中的 ACC_SYNCHRONIZED 访问标志,得到其是否为同步方法。

对于方法中的同步块,JVM 中使用 monitorenter 和 monitorexit 两条指令来支持。下面参见一个代码清单:

public class SyncInstruction { void onlyMe(Object f) { synchronized (f) { System.out.println("synchronized control."); } } }

对应的指令序列如下:

0 aload_1 // 将对象f入栈 1 dup // 复制栈顶元素(即f的引用) 2 astore_2 // 将栈顶元素存储到局变量表Slot 2中 3 monitorenter // 以栈顶元素(f)作为锁,开始同步 4 getstatic #2 <java/lang/System.out> // 访问System的静态属性out 7 ldc #3 <synchronized control.> // 将字符串常量"synchronized control."压入操作数栈顶 9 invokevirtual #4 <java/io/PrintStream.println> // 调用PrintStream.println()方法 12 aload_2 // 将局部变量表Slot 2的元素(f)入栈 13 monitorexit // 退出同步 14 goto 22 ( 8) // 方法正常退出,跳转到22行 17 astore_3 // 这里开始是异常路径,它的偏移量记录在异常表中,如下图所示 18 aload_2 // 将局部变量表Slot 2的元素(f)入栈 19 monitorexit // 退出同步 20 aload_3 // 将局变量表Slot 3的元素(异常对象)入栈 21 athrow // 把异常重新抛出给onlyMe()方法的调用者 22 return // 方法正常返回

异常表如下所示:

程序中中文对应的字节码(字节码指令解释执行)(8)

异常表


参考:《深入理解Java虚拟机》

,