代码、内容参考来自于张秀宏大佬的自己动手写Java虚拟机 (Java核心技术系列)以及尚硅谷宋红康:JVM全套教程。
1.astore指令
- 将一个操作数栈的值存储到数组元素中的指令(修改堆中):bastore、 castore、 sastore、 iastore、lastore、fastore, dastore, aastore
即(如下图:byte和boolean用同一个指令):

astore系列指令按索引给数组元素赋值。在\instructions\stores目录下创建xastore.go文件,在其中定义8条指令,代码如下:
package stores
import (
"jvmgo/ch08/instructions/base"
"jvmgo/ch08/rtda"
"jvmgo/ch08/rtda/heap"
)
// Store into reference array
type AASTORE struct{ base.NoOperandsInstruction }
func (self *AASTORE) Execute(frame *rtda.Frame) {
// 获取操作数栈
stack := frame.OperandStack()
// 弹出第一个操作数,作为要赋给元素的值
ref := stack.PopRef()
// 弹出第二个操作数, 作为数组引用
index := stack.PopInt()
// 弹出第三个操作数,作为数组引用
arrRef := stack.PopRef()
// 判断数组是否为空
checkNotNil(arrRef)
// 获取类型为引用的数组
refs := arrRef.Refs()
// 判断索引是否合法
checkIndex(len(refs), index)
// 通过索引给数组赋值
refs[index] = ref
}
// Store into byte or boolean array
type BASTORE struct{ base.NoOperandsInstruction }
func (self *BASTORE) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
val := stack.PopInt()
index := stack.PopInt()
arrRef := stack.PopRef()
checkNotNil(arrRef)
bytes := arrRef.Bytes()
checkIndex(len(bytes), index)
bytes[index] = int8(val)
}
// Store into char array
type CASTORE struct{ base.NoOperandsInstruction }
func (self *CASTORE) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
val := stack.PopInt()
index := stack.PopInt()
arrRef := stack.PopRef()
checkNotNil(arrRef)
chars := arrRef.Chars()
checkIndex(len(chars), index)
chars[index] = uint16(val)
}
// Store into double array
type DASTORE struct{ base.NoOperandsInstruction }
func (self *DASTORE) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
val := stack.PopDouble()
index := stack.PopInt()
arrRef := stack.PopRef()
checkNotNil(arrRef)
doubles := arrRef.Doubles()
checkIndex(len(doubles), index)
doubles[index] = float64(val)
}
// Store into float array
type FASTORE struct{ base.NoOperandsInstruction }
func (self *FASTORE) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
val := stack.PopFloat()
index := stack.PopInt()
arrRef := stack.PopRef()
checkNotNil(arrRef)
floats := arrRef.Floats()
checkIndex(len(floats), index)
floats[index] = float32(val)
}
// Store into int array
type IASTORE struct{ base.NoOperandsInstruction }
func (self *IASTORE) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
val := stack.PopInt()
index := stack.PopInt()
arrRef := stack.PopRef()
checkNotNil(arrRef)
ints := arrRef.Ints()
checkIndex(len(ints), index)
ints[index] = int32(val)
}
// Store into long array
type LASTORE struct{ base.NoOperandsInstruction }
func (self *LASTORE) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
val := stack.PopLong()
index := stack.PopInt()
arrRef := stack.PopRef()
checkNotNil(arrRef)
longs := arrRef.Longs()
checkIndex(len(longs), index)
longs[index] = int64(val)
}
// Store into short array
type SASTORE struct{ base.NoOperandsInstruction }
func (self *SASTORE) Execute(frame *rtda.Frame) {
stack := frame.OperandStack()
val := stack.PopInt()
index := stack.PopInt()
arrRef := stack.PopRef()
checkNotNil(arrRef)
shorts := arrRef.Shorts()
checkIndex(len(shorts), index)
shorts[index] = int16(val)
}
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条指令的实现是大同小异,以aastore为例进行说明,aastore指令的三个操作数分别是:要赋给数组元素的值、数组索引、数组引用,依次从操作数栈中弹出。如果数组引用是null,则抛出NullPointerException。如果数组索引小于0或者大于等于数组长度,则抛出ArrayIndexOutOfBoundsException异常。这两个检查和aload系列指令一样。如果一切正常,则按索引给数组元素赋值。

2.multianewarray指令
multianewarray指令创建多维数组。multianewarray指令的第一个操作数是个uint16索引,通过这个索引可以从运行时常量池中找到一个类符号引用,解析这个引用就可以得到多维数组类。第二个操作数是个uint8整数,表示数组维度。
整体代码
在/rtda/heap/array_class.go文件中,添加ComponentClass()方法返回数组类的元素类型,ComponentClass()方法先根据数组类名推测出数组元素类名,然后用类加载器加载元素类即可。代码如下:
func (self *Class) ComponentClass() *Class {
componentClassName := getComponentClassName(self.name)
return self.loader.LoadClass(componentClassName)
}
在\rtda\heap\class_name_helper.go文件中,添加getComponentClassName()函数,数组类名以方括号开头,把它去掉就是数组元素的类型描述符,然后把类型描述符转成类名即可。
// [[XXX -> [XXX
// [LXXX; -> XXX
// [I -> int
func getComponentClassName(className string) string {
//数组类名以方括号开头,把它去掉就是数组元素的类型描述符,然后把类型描述符转成类名即可
if className[0] == '[' {
componentTypeDescriptor := className[1:]
return toClassName(componentTypeDescriptor)
}
panic("Not array: " + className)
}
继续在ch08\rtda\heap\class_name_helper.go文件中,添加toClassName()函数,如果类型描述符以方括号开头,那么肯定是数组,描述符即是类名。如果类型描述符以L开头,那么肯定是类描述符,去掉开头的L和末尾的分号即是类名,否则判断是否是基本类型的描述符,如果是,返回基本类型名称,否则调用panic()函数终止程序执行。
// [XXX => [XXX
// LXXX; => XXX
// I => int
func toClassName(descriptor string) string {
//如果类型描述符以方括号开头,那么肯定是数组,描述符即是类名。
if descriptor[0] == '[' {
// array
return descriptor
}
//。如果类型描述符以L开头,那么肯定是类描述符,去掉开头的L和末尾的分号即是类名
if descriptor[0] == 'L' {
// object
return descriptor[1 : len(descriptor)-1]
}
//否则判断是否是基本类型的描述符,如果是,返回基本类型名称,否则调用panic()函数终止程序执行。
for className, d := range primitiveTypes {
if d == descriptor {
// primitive
return className
}
}
panic("Invalid descriptor: " + descriptor)
}
在\instructions\references目录下创建multianewarray.go文件,在其中定义multianewarray指令,代码如下所示:
package references
import (
"jvmgo/ch08/instructions/base"
"jvmgo/ch08/rtda"
"jvmgo/ch08/rtda/heap"
)
// Create new multidimensional array
type MULTI_ANEW_ARRAY struct {
// 第一个操作数
index uint16
// 第二个操作数
dimensions uint8
}
// 读取两个操作数
func (self *MULTI_ANEW_ARRAY) FetchOperands(reader *base.BytecodeReader) {
self.index = reader.ReadUint16()
self.dimensions = reader.ReadUint8()
}
func (self *MULTI_ANEW_ARRAY) Execute(frame *rtda.Frame) {
// 获取运行时常量池
cp := frame.Method().Class().ConstantPool()
// 获取数组类符号引用(这里获取的直接就是数组类符号引用,所以下面可以直接解析)
classRef := cp.GetConstant(uint(self.index)).(*heap.ClassRef)
// 解析数组类符号引用
arrClass := classRef.ResolvedClass()
// 获取操作数栈
stack := frame.OperandStack()
//从操作数栈中弹出n个int值,获取每一维度数组的长度
counts := popAndCheckCounts(stack, int(self.dimensions))
//创建多维数组
arr := newMultiDimensionalArray(counts, arrClass)
stack.PushRef(arr)
}
// 从操作数栈中弹出n个int值
func popAndCheckCounts(stack *rtda.OperandStack, dimensions int) []int32 {
counts := make([]int32, dimensions)
for i := dimensions - 1; i >= 0; i-- {
//// 弹出每一维度数组的长度
counts[i] = stack.PopInt()
//从操作数栈中弹出n个int值,并且确保它们都大于等于0。
if counts[i] < 0 {
panic("java.lang.NegativeArraySizeException")
}
}
return counts
}
// 创建多维数组
func newMultiDimensionalArray(counts []int32, arrClass *heap.Class) *heap.Object {
count := uint(counts[0])
arr := arrClass.NewArray(count)
if len(counts) > 1 {
refs := arr.Refs()
for i := range refs {
refs[i] = newMultiDimensionalArray(counts[1:], arrClass.ComponentClass())
}
}
return arr
}
代码解释
那两个操作数在字节码中紧跟在指令操作码后面,由FetchOperands()方法读取,代码如下:

multianewarray指令还需要从操作数栈中弹出n个整数,分别代表每一个维度的数组长度。Execute()方法根据数组类、数组维度和各个维度的数组长度创建多维数组,代码如下:

在anewarray指令中,解析类符号引用后得到的是数组元素的类,而这里解析出来的直接就是数组类。popAndCheckCounts()函数从操作数栈中弹出n个int值,并且确保它们都大于等于0。如果其中任何一个小于0,则抛出NegativeArraySizeException异常。代码如下:

newMultiArray()函数创建多维数组,代码如下:

示例代码:
package jvmgo.book.ch06;
public class ArrayDemoTest {
public void test(){
int[][][] x = new int[3][4][5];
}
}上面的Java方法创建了一个三维数组,编译之后的字节码如下:

multianewarray指令的第一个操作数是2,是个类引用,类名是[[[I,说明要创建的是int[][][]数组。第二个操作数是3,说明要创建三维数组。当方法执行时,三条iconst_n指令先后把整数3、4、5推入操作数栈顶。
multianewarray指令在解码时就已经拿到常量池索引#2和数组维度3。在执行时,它先查找运行时常量池索引,知道要创建的是int[][][]数组,接着从操作数栈中弹出三个int值,依次是5、4、3。现在multianewarray指令拿到了全部信息,从最外维开始创建数组实例即可。
修改\instructions\factory.go文件,将将新增的指令注释放开。
3.instanceof和checkcast
修改instanceof和checkcast指令
注意:
- 数组可以强制转换成Object类型(因为数组的超类是Object)。
- 数组可以强制转换成Cloneable和Serializable类型(因为数组实现了这两个接口)。
如果下面两个条件之一成立,类型为[]SC的数组可以强制转换成类型为[]TC的数组:
- TC和SC是同一个基本类型。
- TC和SC都是引用类型,且SC可以强制转换成TC
为了方便,在\rtda\heap\class.go文件添加如下方法
func (self *Class) isJlObject() bool {
return self.name == "java/lang/Object"
}
func (self *Class) isJlCloneable() bool {
return self.name == "java/lang/Cloneable"
}
func (self *Class) isJioSerializable() bool {
return self.name == "java/io/Serializable"
}
修改\rtda\heap\class_hierarchy.go文件中的isAssignableFrom方法,下面都加了注释,代码如下:
package heap
// jvms8 6.5.instanceof
// jvms8 6.5.checkcast
func (self *Class) isAssignableFrom(other *Class) bool {
s, t := other, self
if s == t {
return true
}
if !s.IsArray() {
if !s.IsInterface() {
// s is class
if !t.IsInterface() {
// t is not interface
return s.IsSubClassOf(t)
} else {
// t is interface
return s.IsImplements(t)
}
} else {
// s is interface
if !t.IsInterface() {
// t is not interface
return t.isJlObject()
} else {
// t is interface
return t.isSuperInterfaceOf(s)
}
}
} else {
// s is array
if !t.IsArray() {
if !t.IsInterface() {
// t is class
return t.isJlObject()
} else {
// t is interface
return t.isJlCloneable() || t.isJioSerializable()
}
} else {
// t is array
sc := s.ComponentClass()
tc := t.ComponentClass()
return sc == tc || tc.isAssignableFrom(sc)
}
}
return false
}
// self extends c
func (self *Class) IsSubClassOf(other *Class) bool {
for c := self.superClass; c != nil; c = c.superClass {
if c == other {
return true
}
}
return false
}
// self implements iface
func (self *Class) IsImplements(iface *Class) bool {
for c := self; c != nil; c = c.superClass {
for _, i := range c.interfaces {
if i == iface || i.isSubInterfaceOf(iface) {
return true
}
}
}
return false
}
// self extends iface
func (self *Class) isSubInterfaceOf(iface *Class) bool {
for _, superInterface := range self.interfaces {
if superInterface == iface || superInterface.isSubInterfaceOf(iface) {
return true
}
}
return false
}
// c extends self
func (self *Class) IsSuperClassOf(other *Class) bool {
return other.IsSubClassOf(self)
}
// iface extends self
func (self *Class) isSuperInterfaceOf(iface *Class) bool {
return iface.isSubInterfaceOf(self)
}如下图:

4.测试数组
冒泡排序算法测试:
package jvmgo.book.ch06;
public class BubbleSortTest {
public static void main(String[] args) {
int[] arr = {22, 84, 77, 11, 95, 9, 78, 56, 36, 97, 65, 36, 10, 24, 92, 48};
bubbleSort(arr);
printArray(arr);
}
private static void bubbleSort(int[] arr) {
boolean swapped = true;
int j = 0;
int tmp;
while (swapped) {
swapped = false;
j++;
for (int i = 0; i < arr.length - j; i++) {
if (arr[i] > arr[i + 1]) {
tmp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = tmp;
swapped = true;
}
}
}
}
private static void printArray(int[] arr) {
for (int i : arr) {
System.out.println(i);
}
}
}打开命令行窗口,执行下面的命令编译本章代码:
go install jvmgo\ch08
ch08 -classpath D:\MAT_log -Xjre "D:\software\java\jre" BubbleSortTest

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


