代码、内容参考来自于张秀宏大佬的自己动手写Java虚拟机 (Java核心技术系列)以及尚硅谷宋红康:JVM全套教程。
newarray、anewarray、multianewarray和arraylength指令属于引用类指令;aload和astore系列指令各有8条,分别属于加载类和存储类指令。
1.数组指令概述
数组操作指令
数组操作指令主要有: xastore和xaload指令。具体为:
- 把一个数组元素加载到操作数栈的指令: baload、 caload、 saload、 iaload、 laload、faload、daload, aaload
- 将一个操作数栈的值存储到数组元素中的指令(修改堆中):bastore、 castore、 sastore、 iastore、lastore、fastore, dastore, aastore
即(如下图:byte和boolean用同一个指令):

- 取数组长度的指令: arraylength,该指令弹出栈顶的数组元素(索引),获取数组的长度,将长度压入栈。
创建数组的指令
创建数组的指令:newarray、anewarray、multianewarray
- newarray:创建基本类型数组
- anewarray:创建引用类型数组
- multianewarray:创建多维数组
上述创建指令可以用于创建对象或者数组,由于对象和数组在Java中的广泛使用,这些指令的使用频率也非常高。
2. 说明
- 指令xaload表示将数组的元素压栈,比如saload、caload分别表示压入short数组和char数组。指令xaload在执行时,要求操作数中栈顶元素为数组索引,栈顶顺位第2个元素为数组引用a,该指令会弹出栈顶这两个元素,并将a[i]重新压入堆栈。
- xastore则专门针对数组操作,以iastore为例,它用于给一个int数组的给定索引赋值。在iastore执行前,操作数栈顶需要以此准备3个元素:值、索引、数组引用,iastore会弹出这3个值,并将值赋给数组中指定索引的位置。
2.newarray指令
newarray指令用来创建基本类型数组,包括boolean[]、byte[]、char[]、short[]、int[]、long[]、float[]和double[]8种。
整体代码
在/rtda/heap/class.go文件,添加一个Getter方法,获取当前class的ClassLoader
func (self *Class) Loader() *ClassLoader {
return self.loader
}
在\instructions\references目录下创建newarray.go,在其中定义newarray指令,代码如下:
package references
import (
"jvmgo/ch08/instructions/base"
"jvmgo/ch08/rtda"
"jvmgo/ch08/rtda/heap"
)
const (
//Array Type atype
AT_BOOLEAN = 4
AT_CHAR = 5
AT_FLOAT = 6
AT_DOUBLE = 7
AT_BYTE = 8
AT_SHORT = 9
AT_INT = 10
AT_LONG = 11
)
// Create new array
type NEW_ARRAY struct {
atype uint8
}
func (self *NEW_ARRAY) FetchOperands(reader *base.BytecodeReader) {
self.atype = reader.ReadUint8()
}
func (self *NEW_ARRAY) Execute(frame *rtda.Frame) {
// 获取当前帧的操作数栈
stack := frame.OperandStack()
// 弹出栈顶元素,作为数组的长度
count := stack.PopInt()
if count < 0 {
panic("java.lang.NegativeArraySizeException")
}
// 获取当前类的类加载器
classLoader := frame.Method().Class().Loader()
// 加载数组类
arrClass := getPrimitiveArrayClass(classLoader, self.atype)
// 创建数组
arr := arrClass.NewArray(uint(count))
// 将创建的数组指针推入操作数栈
stack.PushRef(arr)
}
// 根据atype加载对应的Class
func getPrimitiveArrayClass(loader *heap.ClassLoader, atype uint8) *heap.Class {
switch atype {
case AT_BOOLEAN:
return loader.LoadClass("[Z")
case AT_BYTE:
return loader.LoadClass("[B")
case AT_CHAR:
return loader.LoadClass("[C")
case AT_SHORT:
return loader.LoadClass("[S")
case AT_INT:
return loader.LoadClass("[I")
case AT_LONG:
return loader.LoadClass("[J")
case AT_FLOAT:
return loader.LoadClass("[F")
case AT_DOUBLE:
return loader.LoadClass("[D")
default:
panic("Invalid atype!")
}
}
代码解释
newarray指令需要两个操作数。第一个操作数是一个uint8整数,在字节码中紧跟在指令操作码后面,表示要创建哪种类型的数组。Java虚拟机规范把这个操作数叫作atype,并且规定了它的有效值。把这些值定义为常量,代码如下:

FetchOperands()方法读取atype的值,代码如下:

newarray指令的第二个操作数是count,从操作数栈中弹出,表示数组长度。Execute()方法根据atype和count创建基本类型数组,如果count小于0,则抛出NegativeArraySizeException异常,否则
根据atype值使用当前类的类加载器加载数组类,然后创建数组对象并推入操作数栈。

getPrimitiveArrayClass()函数根据atype加载对应的Class:

3.anewarray指令
anewarray指令用来创建引用类型数组。
代码及解释
anewarray指令也需要两个操作数。第一个操作数是uint16索引,来自字节码。通过这个索引可以从当前类的运行时常量池中找到一个类符号引用,解析这个符号引用就可以得到数组元素的类。第二个操作数是数组长度,从操作数栈中弹出。
在instructions\references目录下创建anewarray.go文件,在其中定义anewarray指令,代码如下:
package references
import (
"jvmgo/ch08/instructions/base"
"jvmgo/ch08/rtda"
"jvmgo/ch08/rtda/heap"
)
// Create new array of reference
type ANEW_ARRAY struct{ base.Index16Instruction }
//根据数组元素的类型和数组长度创建引用类型数组
func (self *ANEW_ARRAY) Execute(frame *rtda.Frame) {
cp := frame.Method().Class().ConstantPool()
classRef := cp.GetConstant(self.Index).(*heap.ClassRef)
componentClass := classRef.ResolvedClass()
stack := frame.OperandStack()
count := stack.PopInt()
if count < 0 {
panic("java.lang.NegativeArraySizeException")
}
arrClass := componentClass.ArrayClass()
arr := arrClass.NewArray(uint(count))
stack.PushRef(arr)
}上面的代码基本一样,就不解释了。
修改/rtda/heap/class.go文件,添加加载数组类的方法:
func (self *Class) ArrayClass() *Class {
arrayClassName := getArrayClassName(self.name)
return self.loader.LoadClass(arrayClassName)
}在\rtda\heap目录下创建class_name_helper.go文件,在其中实现getArrayClassName()函数,代码如下:
package heap
var primitiveTypes = map[string]string{
"void": "V",
"boolean": "Z",
"byte": "B",
"short": "S",
"int": "I",
"long": "J",
"char": "C",
"float": "F",
"double": "D",
}
// [XXX -> [[XXX
// int -> [I
// XXX -> [LXXX;
func getArrayClassName(className string) string {
//把类名转变成类型描述符,然后在前面加上方括号即可
return "[" + toDescriptor(className)
}
// [[XXX -> [XXX
// [LXXX; -> XXX
// [I -> int
func getComponentClassName(className string) string {
if className[0] == '[' {
componentTypeDescriptor := className[1:]
return toClassName(componentTypeDescriptor)
}
panic("Not array: " + className)
}
// [XXX => [XXX
// int => I
// XXX => LXXX;
func toDescriptor(className string) string {
//如果是数组类名,描述符就是类名,直接返回即可
if className[0] == '[' {
// array
return className
}
//如果是基本类型名,返回对应的类型描述符
if d, ok := primitiveTypes[className]; ok {
// primitive
return d
}
// object
//普通的类名,前面加上方括号,结尾加上分号即可得到类型描述符
return "L" + className + ";"
}对上面的代码进行解释:
Class结构体的ArrayClass()方法返回与类对应的数组类,先根据类名得到数组类名,然后调用类加载器加载数组类即可。

getArrayClassName()函数,把类名转变成类型描述符,然后在前面加上方括号即可。:

如果是数组类名,描述符就是类名,直接返回即可。如果是基本类型名,返回对应的类型描述符,否则肯定是普通的类名,前面加上方括号,结尾加上分号即可得到类型描述符。

primitiveTypes变量定义基本类型和对应类型描述符map

4.arraylength指令
arraylength指令用于获取数组长度。arraylength指令只需要一个操作数,即从操作数栈顶弹出的数组引用。
在instructions\references目录下创建arraylength.go,在其中定义arraylength指令,如果数组引用是null,则需要抛出NullPointerException异常,否则取数组长度,推入操作数栈顶即可。代码如下:
package references
import (
"jvmgo/ch08/instructions/base"
"jvmgo/ch08/rtda"
)
// Get length of array
type ARRAY_LENGTH struct{ base.NoOperandsInstruction }
func (self *ARRAY_LENGTH) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
arrRef := stack.PopRef()
if arrRef == nil {
panic("java.lang.NullPointerException")
}
// 获取数组长度
arrLen := arrRef.ArrayLength()
// 讲数组长度推入操作数栈顶
stack.PushInt(arrLen)
}
5.aload指令
aload系列指令按索引取数组元素值,然后推入操作数栈。
- 把一个数组元素加载到操作数栈的指令: baload、 caload、 saload、 iaload、 laload、faload、daload, aaload

在\instructions\loads目录下创建xaload.go文件,在其中定义8条指令,代码如下:
package loads
import (
"jvmgo/ch08/instructions/base"
"jvmgo/ch08/rtda"
"jvmgo/ch08/rtda/heap"
)
// Load reference from array
type AALOAD struct{ base.NoOperandsInstruction }
func (self *AALOAD) Execute(frame *rtda.Frame) {
// 获取操作数栈
stack := frame.OperandStack()
//先从操作数栈中弹出第一个操作数:数组索引
index := stack.PopInt()
//然后弹出第二个操作数:数组引用
arrRef := stack.PopRef()
// 判断数组是否为空
checkNotNil(arrRef)
// 获取类型为引用的数组
refs := arrRef.Refs()
// 判断索引是否合法
checkIndex(len(refs), index)
// 将通过索引获取的值入栈
stack.PushRef(refs[index])
}
// Load byte or boolean from array
type BALOAD struct{ base.NoOperandsInstruction }
func (self *BALOAD) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
index := stack.PopInt()
arrRef := stack.PopRef()
checkNotNil(arrRef)
bytes := arrRef.Bytes()
checkIndex(len(bytes), index)
stack.PushInt(int32(bytes[index]))
}
// Load char from array
type CALOAD struct{ base.NoOperandsInstruction }
func (self *CALOAD) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
index := stack.PopInt()
arrRef := stack.PopRef()
checkNotNil(arrRef)
chars := arrRef.Chars()
checkIndex(len(chars), index)
stack.PushInt(int32(chars[index]))
}
// Load double from array
type DALOAD struct{ base.NoOperandsInstruction }
func (self *DALOAD) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
index := stack.PopInt()
arrRef := stack.PopRef()
checkNotNil(arrRef)
doubles := arrRef.Doubles()
checkIndex(len(doubles), index)
stack.PushDouble(doubles[index])
}
// Load float from array
type FALOAD struct{ base.NoOperandsInstruction }
func (self *FALOAD) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
index := stack.PopInt()
arrRef := stack.PopRef()
checkNotNil(arrRef)
floats := arrRef.Floats()
checkIndex(len(floats), index)
stack.PushFloat(floats[index])
}
// Load int from array
type IALOAD struct{ base.NoOperandsInstruction }
func (self *IALOAD) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
index := stack.PopInt()
arrRef := stack.PopRef()
checkNotNil(arrRef)
ints := arrRef.Ints()
checkIndex(len(ints), index)
stack.PushInt(ints[index])
}
// Load long from array
type LALOAD struct{ base.NoOperandsInstruction }
func (self *LALOAD) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
index := stack.PopInt()
arrRef := stack.PopRef()
checkNotNil(arrRef)
longs := arrRef.Longs()
checkIndex(len(longs), index)
stack.PushLong(longs[index])
}
// Load short from array
type SALOAD struct{ base.NoOperandsInstruction }
func (self *SALOAD) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
index := stack.PopInt()
arrRef := stack.PopRef()
checkNotNil(arrRef)
shorts := arrRef.Shorts()
checkIndex(len(shorts), index)
stack.PushInt(int32(shorts[index]))
}
// 判断数组引用是否为空
func checkNotNil(ref *heap.Object) {
if ref == nil {
panic("java.lang.NullPointerException")
}
}
// 判断索引范围是否在数组长度内
func checkIndex(arrLen int, index int32) {
if index < 0 || index >= int32(arrLen) {
panic("ArrayIndexOutOfBoundsException")
}
}这8条指令的实现基本一样,以aaload指令为例进行说明。首先从操作数栈中弹出第一个操作数:数组索引,然后弹出第二个操作数:数组引用。如果数组引用是null,则抛出NullPointerException异常。如果数组索引小于0,或者大于等于数组长度,则抛出ArrayIndexOutOfBoundsException。如果一切正常,则按索引取出数组元素,推入操作数栈顶。

checkNotNil()函数判断数组引用是否为空,checkIndex()函数判断索引范围是否在数组长度内

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


