代码、内容参考来自于张秀宏大佬的自己动手写Java虚拟机 (Java核心技术系列)以及尚硅谷宋红康:JVM全套教程。
1.控制指令
控制指令共有11条。jsr和ret指令在Java 6之前用于实现finally子句,从Java 6开始,Oracle的Java编译器已经不再使用这两条指令了,不讨论这两条指令。
这里实现3条指令:goto、tableswitch和lookupswitch。
goto指令
整体代码
在instructions\control目录下创建goto.go文件,在其中定义goto指令,代码如下:
package control
import (
"jvmgo/ch05/instructions/base"
"jvmgo/ch05/rtda"
)
// Branch always
type GOTO struct{ base.BranchInstruction }
func (self *GOTO) Execute(frame *rtda.Frame) {
base.Branch(frame, self.Offset)
}
代码解释
goto指令进行无条件跳转,其Execute()方法如下:

tableswitch指令
多条件分支跳转指令
多条件分支跳转指令是专为switch-case语句设计的,主要有tableswitch和lookupswitch。

从助记符上看,两者都是switch语句的实现,它们的区别:
- tableswitch要求多个条件分支值是连续的,它内部只存放起始值和终止值,以及若干个跳转偏移量,通过给定的操作数index,可以立即定位到跳转偏移量位置,因此效率比较高。
- 指令lookupswitch内部存放着各个离散的case-offset对,每次执行都要搜索全部的case-offset对,找到匹配的case值,并根据对应的offset计算跳转地址,因此效率较低。
指令tableswitch的示意图如下图所示。由于tableswitch的case值是连续的,因此只需要记录最低值和最高值,以及每一项对应的offset偏移量,根据给定的index值通过简单的计算即可直接定位到offset。

指令lookupswitch处理的是离散的case值,但是出于效率考虑,将case-offset对按照case值大小排序,给定index时,需要查找与index相等的case,获得其offset,如果找不到则跳转到default。指令lookupswitch如下图所示。

tableswitch举例:
//3.多条件分支跳转
public void swtich1(int select){
int num;
switch(select){
case 1:
num = 10;
break;
case 2:
num = 20;
break;
case 3:
num = 30;
break;
default:
num = 40;
}
}
缺少一个break情况:
//3.多条件分支跳转
public void swtich1(int select){
int num;
switch(select){
case 1:
num = 10;
break;
case 2:
num = 20;
//break;
case 3:
num = 30;
break;
default:
num = 40;
}
}lookupswitch举例:
public void swtich2(int select){
int num;
switch(select){
case 100:
num = 10;
break;
case 500:
num = 20;
break;
case 200:
num = 30;
break;
default:
num = 40;
}
}
调用hashCode方法,将哈希值与下面字符串的哈希值比较,哈希值也大小排序,升序
//jdk7新特性:引入String类型
public void swtich3(String season){
switch(season){
case "SPRING":break;
case "SUMMER":break;
case "AUTUMN":break;
case "WINTER":break;
}
}


Java语言中的switch-case语句有两种实现方式:如果case值可以编码成一个索引表,则实现成tableswitch指令;否则实现成lookupswitch指令。
整体代码
在instructions\control目录下创建tableswitch.go文件,在其中定义tableswitch指令,代码如下:
package control
import (
"jvmgo/ch05/instructions/base"
"jvmgo/ch05/rtda"
)
/*
tableswitch
<0-3 byte pad>
defaultbyte1
defaultbyte2
defaultbyte3
defaultbyte4
lowbyte1
lowbyte2
lowbyte3
lowbyte4
highbyte1
highbyte2
highbyte3
highbyte4
jump offsets...
*/
// Access jump table by index and jump
type TABLE_SWITCH struct {
defaultOffset int32
low int32
high int32
jumpOffsets []int32
}
func (self *TABLE_SWITCH) FetchOperands(reader *base.BytecodeReader) {
reader.SkipPadding()
self.defaultOffset = reader.ReadInt32()
self.low = reader.ReadInt32()
self.high = reader.ReadInt32()
jumpOffsetsCount := self.high - self.low + 1
self.jumpOffsets = reader.ReadInt32s(jumpOffsetsCount)
}
func (self *TABLE_SWITCH) Execute(frame *rtda.Frame) {
index := frame.OperandStack().PopInt()
var offset int
if index >= self.low && index <= self.high {
offset = int(self.jumpOffsets[index-self.low])
} else {
offset = int(self.defaultOffset)
}
base.Branch(frame, offset)
}tableswitch指令的操作数比较复杂,它的FetchOperands()方法如下:

在class文件,对应位置,第一个字节0xAA表示助记符tableswitch指令,后面两个字节0x00、0x00没有实质意义,tableswitch指令操作码的后面有0~3字节的padding,以保证defaultOffset在字节码中的地址是4的倍数。BytecodeReader结构体的SkipPadding()方法如下,我们之前就加入的方法派上用场:
// used by lookupswitch and tableswitch
func (self *BytecodeReader) SkipPadding() {
for self.pc%4 != 0 {
self.ReadUint8()
}
}
defaultOffset对应默认情况下执行跳转所需的字节码偏移量;low和high记录case的取值范围;jumpOffsets是一个索引表,里面存放high-low+1个int值,对应各种case情况下,执行跳转所需的字节码偏移量。BytecodeReader结构体的ReadInt32s()方法如下,我们之前就加入的方法派上用场:
// used by lookupswitch and tableswitch
func (self *BytecodeReader) ReadInt32s(n int32) []int32 {
ints := make([]int32, n)
for i := range ints {
ints[i] = self.ReadInt32()
}
return ints
}
Execute()方法先从操作数栈中弹出一个int变量,然后看它是否在low和high给定的范围之内。如果在,则从jumpOffsets表中查出偏移量进行跳转,否则按照defaultOffset跳转。代码如下:

lookupswitch指令
整体代码
在instructions\control目录下创建lookupswitch.go文件,在其中定义lookupswitch指令,代码如下:
package control
import (
"jvmgo/ch05/instructions/base"
"jvmgo/ch05/rtda"
)
/*
lookupswitch
<0-3 byte pad>
defaultbyte1
defaultbyte2
defaultbyte3
defaultbyte4
npairs1
npairs2
npairs3
npairs4
match-offset pairs...
*/
// Access jump table by key match and jump
type LOOKUP_SWITCH struct {
defaultOffset int32
npairs int32
matchOffsets []int32
}
func (self *LOOKUP_SWITCH) FetchOperands(reader *base.BytecodeReader) {
reader.SkipPadding()
self.defaultOffset = reader.ReadInt32()
self.npairs = reader.ReadInt32()
self.matchOffsets = reader.ReadInt32s(self.npairs * 2)
}
func (self *LOOKUP_SWITCH) Execute(frame *rtda.Frame) {
key := frame.OperandStack().PopInt()
for i := int32(0); i < self.npairs*2; i += 2 {
if self.matchOffsets[i] == key {
offset := self.matchOffsets[i+1]
base.Branch(frame, int(offset))
return
}
}
base.Branch(frame, int(self.defaultOffset))
}
代码解释
FetchOperands()方法也要先跳过padding,代码如下:


2.扩展指令
multianewarray指令用于创建多维数组,在第8章讨论数组时实现该指令。本节实现剩下的4条指令。
wide指令
加载类指令、存储类指令、ret指令和iinc指令需要按索引访问局部变量表,索引以uint8的形式存在字节码中。对于大部分方法来说,局部变量表大小都不会超过256,所以用一字节来表示索引就够了。如果有方法的局部变量表超过256,Java虚拟机规范定义了wide指令来扩展前述指令。
整体代码
在instructions\extended目录下创建wide.go文件,在其中定义wide指令,代码如下:
package extended
import (
"jvmgo/ch05/instructions/base"
"jvmgo/ch05/instructions/loads"
"jvmgo/ch05/instructions/math"
"jvmgo/ch05/instructions/stores"
"jvmgo/ch05/rtda"
)
// Extend local variable index by additional bytes
type WIDE struct {
modifiedInstruction base.Instruction
}
func (self *WIDE) FetchOperands(reader *base.BytecodeReader) {
opcode := reader.ReadUint8()
switch opcode {
case 0x15:
inst := &loads.ILOAD{}
inst.Index = uint(reader.ReadUint16())
self.modifiedInstruction = inst
case 0x16:
inst := &loads.LLOAD{}
inst.Index = uint(reader.ReadUint16())
self.modifiedInstruction = inst
case 0x17:
inst := &loads.FLOAD{}
inst.Index = uint(reader.ReadUint16())
self.modifiedInstruction = inst
case 0x18:
inst := &loads.DLOAD{}
inst.Index = uint(reader.ReadUint16())
self.modifiedInstruction = inst
case 0x19:
inst := &loads.ALOAD{}
inst.Index = uint(reader.ReadUint16())
self.modifiedInstruction = inst
case 0x36:
inst := &stores.ISTORE{}
inst.Index = uint(reader.ReadUint16())
self.modifiedInstruction = inst
case 0x37:
inst := &stores.LSTORE{}
inst.Index = uint(reader.ReadUint16())
self.modifiedInstruction = inst
case 0x38:
inst := &stores.FSTORE{}
inst.Index = uint(reader.ReadUint16())
self.modifiedInstruction = inst
case 0x39:
inst := &stores.DSTORE{}
inst.Index = uint(reader.ReadUint16())
self.modifiedInstruction = inst
case 0x3a:
inst := &stores.ASTORE{}
inst.Index = uint(reader.ReadUint16())
self.modifiedInstruction = inst
case 0x84:
inst := &math.IINC{}
inst.Index = uint(reader.ReadUint16())
inst.Const = int32(reader.ReadInt16())
self.modifiedInstruction = inst
case 0xa9: // ret
panic("Unsupported opcode: 0xa9!")
}
}
func (self *WIDE) Execute(frame *rtda.Frame) {
self.modifiedInstruction.Execute(frame)
}
代码解释
wide指令改变其他指令的行为,modifiedInstruction字段存放被改变的指令。wide指令需要自己解码出modifiedInstruction,FetchOperands()方法先从字节码中读取一字节的操作码,然后创建子指令实例,最后读取子指令的操作数。因为没有实现ret指令,所以暂时调用panic()函数终止程序执行。加载指令和存储指令都只有一个操作数,需要扩展成2字节,以iload为例:

iinc指令有两个操作数,都需要扩展成2字节,代码如下:

wide指令只是增加了索引宽度,并不改变子指令操作,所以其Execute()方法只要调用子指令的Execute方法即可:

ifnull和ifnonnull指令
整体代码
在instructions\extended目录下创建ifnull.go文件,在其中定义ifnull和ifnonnull指令,代码如下:
package extended
import (
"jvmgo/ch05/instructions/base"
"jvmgo/ch05/rtda"
)
// Branch if reference is null
type IFNULL struct{ base.BranchInstruction }
func (self *IFNULL) Execute(frame *rtda.Frame) {
ref := frame.OperandStack().PopRef()
if ref == nil {
base.Branch(frame, self.Offset)
}
}
// Branch if reference not null
type IFNONNULL struct{ base.BranchInstruction }
func (self *IFNONNULL) Execute(frame *rtda.Frame) {
ref := frame.OperandStack().PopRef()
if ref != nil {
base.Branch(frame, self.Offset)
}
}
代码解释
根据引用是否是null进行跳转,ifnull和ifnonnull指令把栈顶的引用弹出。以ifnull指令为例,它的Execute方法如下:

goto_w指令
整体代码
在instructions\extended目录下创建goto_w.go文件,在其中定义goto_w指令,代码如下:
package extended
import (
"jvmgo/ch05/instructions/base"
"jvmgo/ch05/rtda"
)
// Branch always (wide index)
type GOTO_W struct {
offset int
}
func (self *GOTO_W) FetchOperands(reader *base.BytecodeReader) {
self.offset = int(reader.ReadInt32())
}
func (self *GOTO_W) Execute(frame *rtda.Frame) {
base.Branch(frame, self.offset)
}
代码解释
goto_w指令和goto指令的唯一区别就是索引从2字节变成了4字节。FetchOperands()方法代码如下:

3.参考
尚硅谷宋红康:JVM全套教程:https://www.bilibili.com/video/BV1PJ411n7xZ
周志明:深入理解java虚拟机
张秀宏:自己动手写Java虚拟机 (Java核心技术系列)



