手写JVM(二十四)-数组指令(二)

代码、内容参考来自于张秀宏大佬的自己动手写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核心技术系列)

GO语言官网:Standard library – Go Packages

Java虚拟机规范:Chapter 4. The class File Format (oracle.com)

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇