用Java手写JVM第八章——数组和字符串

本文详细探讨了Java虚拟机中数组的实现原理,包括数组对象的构造、数组类的加载以及与之相关的指令,如newarray、anewarray和arraylength。同时,作者还介绍了字符串的处理,涉及字符串池、ldc指令的完善以及类加载器的更新。通过实例演示,展示了如何测试数组和字符串的操作。
摘要由CSDN通过智能技术生成

本章分为两部分,前半部分讨论数组和数组相关指令,后半部分讨论字符串。Java虚拟机直接支持数组,对字符串的支持则由java.lang.String和相关的类提供。

代码目录

ZYX-demo-jvm-08
├── pom.xml
└── src
    └── main
    │    └── java
    │        └── org.ZYX.demo.jvm
    │             ├── classfile
    │             │   ├── attributes  
    │             │   ├── constantpool 
    │             │   ├── ClassFile.java
    │             │   ├── ClassReader.java
    │             │   └── MemberInfo.java   
    │             ├── classpath
    │             │   ├── impl
    │             │   │   ├── CompositeEntry.java
    │             │   │   ├── DirEntry.java 
    │             │   │   ├── WildcardEntry.java 
    │             │   │   └── ZipEntry.java    
    │             │   ├── Classpath.java
    │             │   └── Entry.java   
    │             ├── instruction
    │             │   ├── base
    │             │   │   ├── BytecodeReader.java
    │             │   │   ├── ClassInitLogic.java
    │             │   │   ├── Instruction.java
    │             │   │   ├── InstructionBranch.java
    │             │   │   ├── InstructionIndex8.java
    │             │   │   ├── InstructionIndex16.java
    │             │   │   ├── InstructionNoOperands.java	
    │             │   │   └── MethodInvokeLogic.java
    │             │   ├── comparisons
    │             │   ├── constants
    │             │   ├── control
    │             │   ├── conversions
    │             │   ├── extended
    │             │   ├── loads
    │             │   ├── math
    │             │   ├── references
    │             │   │   ├── ANEW_ARRAY.java
    │             │   │   ├── ARRAY_LENGTH.java
    │             │   │   ├── CHECK_CAST.java
    │             │   │   ├── GET_FIELD.java
    │             │   │   ├── GET_STATIC.java
    │             │   │   ├── INSTANCE_OF.java
    │             │   │   ├── INVOKE_INTERFACE.java
    │             │   │   ├── INVOKE_SPECIAL.java
    │             │   │   ├── INVOKE_STATIC.java
    │             │   │   ├── INVOKE_VIRTUAL.java
    │             │   │   ├── MULTI_ANEW_ARRAY.java
    │             │   │   ├── NEW.java
    │             │   │   ├── NEW_ARRAY.java
    │             │   │   ├── PUT_FIELD.java
    │             │   │   └── PUT_STATIC.java
    │             │   ├── stack
    │             │   ├── store
    │             │   │   └── xastore
    │             │   │       ├── AASTORE.java	
    │             │   │       ├── BASTORE.java	
    │             │   │       ├── CASTORE.java	
    │             │   │       ├── DASTORE.java
    │             │   │       ├── FASTORE.java
    │             │   │       ├── IASTORE.java
    │             │   │       ├── LASTORE.java	
    │             │   │       └── SASTORE.java		
    │             │   └── Factory   
    │             ├── rtda
    │             │   ├── heap
    │             │   │   ├── constantpool
    │             │   │   ├── methodarea
    │             │   │   │   ├── Class.java    
    │             │   │   │   ├── ClassMember.java  
    │             │   │   │   ├── Field.java    
    │             │   │   │   ├── Method.java 
    │             │   │   │   ├── MethodDescriptor.java 
    │             │   │   │   ├── MethodDescriptorParser.java 
    │             │   │   │   ├── MethodLookup.java 	
    │             │   │   │   ├── Object.java   
    │             │   │   │   ├── Slots.java   
    │             │   │   │   └── StringPool.java	
    │             │   │   └── ClassLoader.java  
    │             │   ├── Frame.java
    │             │   ├── JvmStack.java
    │             │   ├── LocalVars.java
    │             │   ├── OperandStack.java
    │             │   ├── Slot.java 
    │             │   └── Thread.java
    │             ├── Cmd.java
    │             ├── Interpret.java    
    │             └── Main.java
    └── test
         └── java
             └── org.ZYX.demo.test
			     ├── BubbleSortTest.java
                 └── HelloWorld.java

一、数组概述

数组在Java虚拟机中是给比较特殊的概念,主要有以下原因;

首先,数组类和普通的类是不同的。普通的类从class文件中加载,但是数组类由Java虚拟机在运行时生成。数组的类名是左方括号([)+数组元素的类型描述符;数组的类型描述符就是类名本身。例如,int[]的类名是[I,int[][]的类名是[[I,Object[]的类名是[Ljava/lang/Object;,String[][]的类名是[[java/lang/String;等等。

其次,创建数组的方式和创建普通对象的方式不同。普通对象new指令创建,然后由构造函数初始化。基本类型数组由newarray指令创建;引用类型数组由anewarray指令创建;另外还有一个专门的mulitianewarray指令用于创建多维数组。

最后,很显然,数组和普通对象存在的数据也是不同的。普通对象中存放的是实例变量,通过putfield和getfield指令存取。数组对象中存放的则是数组元素,通过aload和astore系列指令按索引存取。其中可以是a、b、c、d、f、i、l或者s,分别用于存取引用、byte、char、double、float、int、long或者shore类型的数组。另外,还有一个arraylength指令,用于获取数组长度。

二、数组实现

将在类和对象的基础上实现数组类和数组对象,先来看数组对象。

1、数组对象

和普通对象一样,数组也是分配在堆中的,通过引用来使用。所以需要改造Object类,让它既可以表示普通的对象,也可以表示数组。打开rtda\heap\object,改动如下:

  Class clazz;
  java.lang.Object data;

把fields字段改为data,类型也从Slots变成了java.lang.Object。

再增加一个Object()方法用来创建数组对象,如下:

  public Object(Class clazz, java.lang.Object data) {
        this.clazz = clazz;
        this.data = data;
    }

需要给Object类添加几个数组特有的方法,如下:

    public byte[] bytes() {
        return (byte[]) this.data;
    }

    public short[] shorts() {
        return (short[]) this.data;
    }

    public int[] ints() {
        return (int[]) this.data;
    }

    public long[] longs() {
        return (long[]) this.data;
    }

    public char[] chars() {
        return (char[]) this.data;
    }

    public float[] floats() {
        return (float[]) this.data;
    }

    public double[] doubles() {
        return (double[]) this.data;
    }

    public Object[] refs() {
        return (Object[]) this.data;
    }

上面这8个方法分别针对引用类型数组和7种基本类型数组返回具体的数组数据。继续编辑在其中添加arrayLength()方法,用于获得返回的数组的长度。代码如下:

  public int arrayLength() {

        if (this.data instanceof byte[]) {
            return ((byte[]) this.data).length;
        }

        if (this.data instanceof short[]) {
            return ((short[]) this.data).length;
        }

        if (this.data instanceof int[]) {
            return ((int[]) this.data).length;
        }

        if (this.data instanceof long[]) {
            return ((long[]) this.data).length;
        }

        if (this.data instanceof char[]) {
            return ((char[]) this.data).length;
        }

        if (this.data instanceof float[]) {
            return ((float[]) this.data).length;
        }

        if (this.data instanceof double[]) {
            return ((double[]) this.data).length;
        }

        if (this.data instanceof Object[]) {
            return ((Object[]) this.data).length;
        }

        throw new RuntimeException("Not array");

    }

2、数组类

不需要修改Class类,只给它添加几个数组特有的方法即可。在其中定义newArray()方法,代码如下:

   public Object newArray(int count) {
        if (!this.isArray()) {
            throw new RuntimeException("Not array class " + this.name);
        }
        switch (this.name()) {
            case "[Z":
                return new Object(this, new byte[count]);
            case "[B":
                return new Object(this, new byte[count]);
            case "[C":
                return new Object(this, new char[count]);
            case "[S":
                return new Object(this, new short[count]);
            case "[I":
                return new Object(this, new int[count]);
            case "[J":
                return new Object(this, new long[count]);
            case "[F":
                return new Object(this, new float[count]);
            case "[D":
                return new Object(this, new double[count]);
            default:
                return new Object(this, new Object[count]);
        }
    }

newArray()方法专门用来创建数组对象。如果类并不是数组类,就终止程序执行,否则根据数组类型创建数组对象。注意:布尔数组是使用字节数组来表示的。

继续编辑,在其中定义isArray()方法,代码如下:

 public boolean isArray() {
        return this.name.getBytes()[0] == '[';
    }

还会实现其他几个方法,等用到时再介绍。下面修改类加载器,让它可以加载数组类。

3、加载数组类

打开rtda\heap\ClassLoader类文件,修改loadClass()方法,改动如下:

   public Class loadClass(String className) {
        Class clazz = classMap.get(className);
        if (null != clazz) return clazz;

        //'['数组标识
        if (className.getBytes()[0] == '[') {
            return this.loadArrayClass(className);
        }

        try {
            return loadNonArrayClass(className);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return null;
    }

这里增加了类型判断,如果要加载的类是数组类,则调用新的loadArrayClass()方法,否则还按照原来的逻辑。loadArrayClass()方法需要生成一个Class类实例,代码如下:

   private Class loadArrayClass(String className) {
        Class clazz = new Class(AccessFlags.ACC_PUBLIC,
                className,
                this,//loader;
                true,//initStarted,因为数组不需要初始化,所以设为true;
                this.loadClass("java/lang/Object"),//数组的父类;
                //数组实现的接口
                new Class[]{this.loadClass("java/lang/Cloneable"), this.loadClass("java/io/Serializable")});
        this.classMap.put(className, clazz);
        return clazz;
    }

三、数组相关指令

本节要实现20条指令,其中newarray、anewarray、multianewarray和arraylength指令属于引用类指令;xaload和xastore系列指令各有8条,分别属于加载类和存储类指令。

①newarray指令

newarray指令用来创建基本类型数组,包括boolean[]、byte[]、char[]、short[]、int[]、long[]、float[]和double[]8种。在instructions\references目录下创建NEW_ARRAY,在其中定义newarray指令,代码如下:

public class NEW_ARRAY implements Instruction {

    private byte atype;

第一个操作数atype在字节码中紧跟在指令操作码后面,表示要创建哪种类型的数组。Java虚拟机规范规定了它的有效值。把这些值定义为常量,代码如下:

static class ArrayType {
        static final byte AT_BOOLEAN = 4;
        static final byte AT_CHAR = 5;
        static final byte AT_FLOAT = 6;
        static final byte AT_DOUBLE = 7;
        static final byte AT_BYTE = 8;
        static final byte AT_SHORT = 9;
        static final byte AT_INT = 10;
        static final byte AT_LONG = 11;
    }

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

public void fetchOperands(BytecodeReader reader) {
        this.atype = reader.readByte();
    }

newarray指令的第二个操作数是count,从操作数栈中弹出,表示数组长度。execute()方法根据atype和count创建基本类型数组,代码如下:

public void execute(Frame frame) {
        OperandStack stack = frame.operandStack();
        int count = stack.popInt();
        if (count < 0) {
            throw new NegativeArraySizeException();
        }

        ClassLoader classLoader = frame.method().clazz().loader();
        Class arrClass = getPrimitiveArrayClass(classLoader, this.atype);
        Object arr = arrClass.newArray(count);
        stack.pushRef(arr);

    }

如果count小于0,则抛出NegativeArraySizeException异常,否则根据atype值使用当前类的类加载器加载数组类,然后创建数组对象并推入操作数栈。getPrimitiveArrayClass()函数的代码如下:

private Class getPrimitiveArrayClass(ClassLoader loader, byte atype){
        switch (atype) {
            case ArrayType.AT_BOOLEAN:
                return loader.loadClass("[Z");
            case ArrayType.AT_BYTE:
                return loader.loadClass("[B");
            case ArrayType.AT_CHAR:
                return loader.loadClass("[C");
            case ArrayType.AT_SHORT:
                return loader.loadClass("[S");
            case ArrayType.AT_INT:
                return loader.loadClass("[I");
            case ArrayType.AT_LONG:
                return loader.loadClass("[J");
            case ArrayType.AT_FLOAT:
                return loader.loadClass("[F");
            case ArrayType.AT_DOUBLE:
                return loader.loadClass("[D");
            default:
                throw new RuntimeException("Invalid atype!");
        }
    }

②anewarray指令

anewarray指令用来创建引用类型数组。在instructions\references目录下创建ANEW_ARRAY文件,在其中定义anewarray指令,代码如下:

public class ANEW_ARRAY extends InstructionIndex16 {

    @Override
    public void execute(Frame frame) {
        
        RunTimeConstantPool runTimeConstantPool = frame.method().clazz().constantPool();
        ClassRef classRef = (ClassRef) runTimeConstantPool.getConstants(this.idx);
        Class componentClass = classRef.resolvedClass();

        OperandStack stack = frame.operandStack();
        int count = stack.popInt();
        if (count < 0) {
            throw new NegativeArraySizeException();
        }

        Class arrClass = componentClass.arrayClass();
        Object arr = arrClass.newArray(count);
        stack.pushRef(arr);

    }

}

也是需要两个操作数,idx和count。第一个操作数是uint16索引,来自字节码。通过这个索引可以从当前类的运行时常量池中找到一个类符号引用,解析这个符号引用就可以得到数组元素的类。第二个操作数是数组长度,从操作数栈中弹出。Execute()方法根据数组元素的类型和数组长度创建引用类型数组。

Class类的arrayClass()方法返回与类对应的数组类,代码如下所示:

 public Class arrayClass() {
        String arrayClassName = ClassNameHelper.getArrayClassName(this.name);
        return this.loader.loadClass(arrayClassName);
    }

先根据类名得到数组类名,然后调用类加载器加载数组类即可。

先根据类名得到数组类名,然后调用类加载器加载数组类即可。在rtda\heap目录下创建ClassNameHelper类文件,在其中实现getArrayClassName()函数,代码如下:

  static String getArrayClassName(String className) {
        return "[" + toDescriptor(className);
    }

把类名转变成类型描述符,然后在前面加上方括号即可。在ClassNameHelper类文件中实现toDescriptor()函数,代码如下:

   private static String toDescriptor(String className) {
        if (className.getBytes()[0] == '[') {
            return className;
        }

        String d = primitiveTypes.get(className);
        if (null != d) return d;

        return "L" + className + ";";
    }

如果是数组类名,描述符就是类名,直接返回即可。如果是基本类型名,返回对应的类型描述符,否则肯定是普通的类名,前面加上方括号,结尾加上分号即可得到类型描述符。primitiveTypes变量也在此类中定义,代码如下:

static Map<String, String> primitiveTypes = new HashMap<String, String>() {
        {
            put("void", "V");
            put("boolean", "Z");
            put("byte", "B");
            put("short", "S");
            put("int", "I");
            put("long", "J");
            put("char", "C");
            put("float", "F");
            put("double", "D");
        }
    };

除了newarray和anewarray,还有一个multianewarray指令,专门用来创建多维数组。这个指令比较复杂,放到最后实现。下面来看arraylength指令。

③arraylength指令

arraylength指令用于获取数组长度。在instructions\references目录下创建ARRAY_LENGTH类,在其中定义arraylength指令,代码如下:

public class ARRAY_LENGTH extends InstructionNoOperands {

    @Override
    public void execute(Frame frame) {

        OperandStack stack = frame.operandStack();
        Object arrRef = stack.popRef();
        if (null == arrRef){
            throw new NullPointerException();
        }

        int arrLen = arrRef.arrayLength();
        stack.pushInt(arrLen);
    }

}

arraylength指令只需要一个操作数,即从操作数栈顶弹出的数组引用。Execute()方法把数组长度推入操作数栈顶。

④xaload指令

xaload系列指令按索引取数组元素值,然后推入操作数栈。在instructions\loads目录下创建xaload包,在其中定义8条指令,这8条指令的实现大同小异,为了节约篇幅,以aaload指令为例进行说明。其Execute()方法如下:

public class AALOAD extends InstructionNoOperands {

    @Override
    public void execute(Frame frame) {
        OperandStack stack = frame.operandStack();
        int idx = stack.popInt();
        Object arrRef = stack.popRef();

        checkNotNull(arrRef);
        Object[] refs = arrRef.refs();
        checkIndex(refs.length, idx);
        stack.pushRef(refs[idx]);
    }

}

首先从操作数栈中弹出第一个操作数:数组索引,然后弹出第二个操作数:数组引用。如果数组引用是null,则抛出NullPointerException异常。这个判断在checkNotNull()函数中。

   protected void checkNotNull(Object ref) {
        if (null == ref) {
            throw new RuntimeException();
        }
    }

如果数组索引小于0,或者大于等于数组长度,则抛ArrayIndexOutOfBoundsException。这个检查在checkIndex()函数中,代码如下:

   protected void checkIndex(int arrLen, int idx) {
        if (idx < 0 || idx >= arrLen) {
            throw new ArrayIndexOutOfBoundsException();
        }
    }

如果一切正常,则按索引取出数组元素,推入操作数栈顶。

⑤xastore指令

xastore系列指令按索引取数组元素值,然后推入操作数栈。在instructions\loads目录下创建xaload包,在其中定义8条指令,这8条指令的实现大同小异,为了节约篇幅,以iastore指令为例进行说明。其Execute()方法如下:

public class IASTORE extends InstructionNoOperands {

    @Override
    public void execute(Frame frame) {
        OperandStack stack = frame.operandStack();
        int val = stack.popInt();
        int idx = stack.popInt();
        Object arrRef = stack.popRef();

        checkNotNull(arrRef);
        int[] ints = arrRef.ints();
        checkIndex(ints.length, idx);
        ints[idx] = val;

    }

}

iastore指令的三个操作数分别是:要赋给数组元素的值、数组索引、数组引用,依次从操作数栈中弹出。如果数组引用是null,则抛出NullPointerException。如果数组索引小于0或者大于等于数组长度,则抛出ArrayIndexOutOfBoundsException异常。

⑥multianewarray指令

multianewarray指令创建多维数组。在instructions\references目录下创建
MULTI_ANEW_ARRAY类文件,在其中定义multianewarray指令,代码如下所示:

public class MULTI_ANEW_ARRAY implements Instruction {

    private short idx;
    private byte dimensions;//表示数组维度;

两个操作数在字节码中紧跟在指令操作码后面,由fetchOperands()方法读取,代码如下:

 public void fetchOperands(BytecodeReader reader) {
        this.idx = reader.readShort();
        this.dimensions = reader.readByte();
    }

multianewarray指令还需要从操作数栈中弹出n个整数,分别代表每一个维度的数组长度。Execute()方法根据数组类、数组维度和各个维度的数组长度创建多维数组。注意,在anewarray指令中,解析类符号引用后得到的是数组元素的类,而这里解析出来的直接就是数组类。代码如下:

public void execute(Frame frame) {
        RunTimeConstantPool runTimeConstantPool = frame.method().clazz().constantPool();
        ClassRef classRef = (ClassRef) runTimeConstantPool.getConstants((int) this.idx);
        Class arrClass = classRef.resolvedClass();

        OperandStack stack = frame.operandStack();
        int[] counts = popAndCheckCounts(stack, this.dimensions);
        Object arr = newMultiDimensionalArray(counts, arrClass);
        stack.pushRef(arr);

    }

popAndCheckCounts()函数从操作数栈中弹出n个int值,并且确保它们都大于等于0。如果其中任何一个小于0,则抛出NegativeArraySizeException异常。代码如下:

 private int[] popAndCheckCounts(OperandStack stack, int dimensions) {
        int[] counts = new int[dimensions];
        for (int i = dimensions - 1; i >= 0; i--) {
            counts[i] = stack.popInt();
            if (counts[i] < 0) {
                throw new NegativeArraySizeException();
            }
        }

        return counts;
    }

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

   private Object newMultiDimensionalArray(int[] counts, Class arrClass) {
        int count = counts[0];
        Object arr = arrClass.newArray(count);
        if (counts.length > 1) {
            Object[] refs = arr.refs();
            for (int i = 0; i < refs.length; i++) {
                int[] copyCount = new int[counts.length - 1];
                System.arraycopy(counts, 1, copyCount, 0, counts.length - 1);
                refs[i] = newMultiDimensionalArray(copyCount, arrClass.componentClass());
            }
        }

        return arr;
    }

我们用个例子来分析:

 public void test() {
 int[][][] x = new int[3][4][5];
}

上面的Java方法创建了一个三维数组,编译之后的字节码如下:

00 iconst_3
01 iconst_4
02 iconst_5
03 multianewarray #5 // [[[I, 3
07 astore_1
08 return

当方法执行时,三条iconst_n指令先后把整数3、4、5推入操作数栈顶。multianewarray指令在解码时就已经拿到常量池索引(5)和数组维度(3)。在执行时,它先查找运行时常量池索引,知道要创建的是int[][][]数组,接着从操作数栈中弹出三个int值,依次是5、4、3。现在multianewarray指令拿到了全部信息,从最外维开始创建数组实例即可。

下面修改instanceof和checkcast,让这两条指令可以正确用于数组对象。

⑦完善instanceof和checkcast指令

虽然说是完善instanceof和checkcast指令,但实际上这两条指令的代码都没有任何变化。需要增加的是rtda\heap\methodarea\ClassHierarchy类中的isAssignableFrom()方法,代码如下:

 public boolean isAssignableFrom(Class other) {
        Class s = this.clazz;
        Class t = other;

        if (s == t) {
            return true;
        }

        if (!s.isArray()) {
            if (!s.isInterface()) {
                if (!t.isInterface()) {
                    return s.isSubClassOf(t);
                } else {
                    return isImplements(t);
                }
            } else {
                if (!t.isInterface()) {
                    return t.isJlObject();
                } else {
                    return t.isSubInterfaceOf(s);
                }
            }
        } else {
            if (!t.isArray()) {

                if (!t.isInterface()) {
                    return t.isJlObject();
                } else {
                    return t.isJlCloneable() || t.isJioSerializable();
                }
            } else {
                Class sc = s.componentClass();
                Class tc = t.componentClass();
                return sc == tc || tc.isAssignableFrom(sc);
            }
        }
    }

由于篇幅限制,就不详细解释这个函数了。需要注意的是:
1、数组可以强制转换成Object类型(因为数组的父类是Object)。
2、数组可以强制转换成Cloneable和Serializable类型(因为数组实现了这两个接口)。
3、如果下面两个条件之一成立,类型为[]SC的数组可以强制转换成类型为[]TC的数组:
·TC和SC是同一个基本类型。
·TC和SC都是引用类型,且SC可以强制转换成TC。

四、测试数组

数组相关的内容差不多都准备好了,下面用经典的冒泡排序算法测试。

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
        };

        //printArray(arr);
        bubbleSort(arr);
        //System.out.println(123456789);
        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);
        }
    }
    
}

结果如下:

"C:\Program Files\Java\jdk1.8.0_281\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.3\lib\idea_rt.jar=52334:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_281\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\rt.jar;D:\JavaProject\Idea-project\itstack-demo-jvm-master\itstack-demo-jvm-08\target\classes;D:\JAVA\maven\repository\com\beust\jcommander\1.72\jcommander-1.72.jar;D:\JAVA\maven\repository\org\projectlombok\lombok\1.18.0\lombok-1.18.0.jar;D:\JAVA\maven\repository\com\alibaba\fastjson\1.2.40\fastjson-1.2.40.jar" org.itstack.demo.jvm.Main -Xjre "C:\Program Files\Java\jdk1.8.0_281\jre" D:\JavaProject\Idea-project\itstack-demo-jvm-master\itstack-demo-jvm-08\src\test\java\org\itstack\demo\test\BubbleSortTest
9
10
11
22
24
36
36
48
56
65
77
78
84
92
95
97

五、字符串

在class文件中,字符串是以MUTF8格式保存的。在Java虚拟机运行期间,字符串以java.lang.String(后面简称String)对象的形式存在,而在String对象内部,字符串又是以UTF16格式保存的。

String类有两个实例变量。其中一个是value,类型是字符数组,用于存放UTF16编码后的字符序列。另一个是hash,缓存计字符串的哈希码,代码如下:

package java.lang;public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
//The value is used for character storage. 
private final char value[];
// Cache the hash code for the string 
private int hash; // Default to 0
... // 其他代码
}

字符串对象是不可变(有final修饰)的,一旦构造好之后,就无法再改变其状态(这里指value字段)。String类有很多构造函数,其中一个是根据字符数组来创建String实例,代码如下:

public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}

本节将参考上面的构造函数,直接创建String实例。为了节约内存,Java虚拟机内部维护了一个字符串池。String类提供了intern()实例方法,可以把自己放入字符串池。这是个本地方法,第九章实现。代码如下:

public native String intern();

1、字符串池

在rtda\heap\methodarea目录下创建stringPool类文件,在其中定义internedStrs变量,代码如下:

public class StringPool {

    private static Map<String, Object> internedStrs = new HashMap<>();

    public static Object jString(ClassLoader loader, String goStr) {
        Object internedStr = internedStrs.get(goStr);
        if (null != internedStr) return internedStr;

        char[] chars = goStr.toCharArray();
        Object jChars = new Object(loader.loadClass("[C"), chars);
        
        Object jStr = loader.loadClass("java/lang/String").newObject();
        jStr.setRefVal("value", "[C", jChars);

        internedStrs.put(goStr, jStr);
        return jStr;
    }

    public static String goString(Object jStr) {
        Object charArr = jStr.getRefVar("value", "[C");
        return new String(charArr.chars());
    }

}

2、完善ldc指令

打开instructions\constants\ldc类文件,修改_ldc()函数,改动如下:

 public void execute(Frame frame) {
        _ldc(frame, this.idx);
    }

    private void _ldc(Frame frame, int idx) {
        OperandStack stack = frame.operandStack();
        Class clazz = frame.method().clazz();
        RunTimeConstantPool runTimeConstantPool = frame.method().clazz().constantPool();
        java.lang.Object c = runTimeConstantPool.getConstants(idx);

        if (c instanceof Integer) {
            stack.pushInt((Integer) c);
            return;
        }

        if (c instanceof Float) {
            stack.pushFloat((Float) c);
            return;
        }

        if (c instanceof String) {
            Object internedStr = StringPool.jString(clazz.loader(), (String) c);
            stack.pushRef(internedStr);
        }

        throw new RuntimeException("todo ldc");
    }

}

如果ldc试图从运行时常量池中加载字符串常量,则先通过常量拿到Java字符串,然后把它转成Java字符串实例并把引用推入操作数栈顶。

3、完善类加载器

打开rtda\heap\classLoader类文件,修改initStaticFinalVar()函数,改动如下:

 case "Ljava/lang/String;":
                    String goStr = (String) constantPool.getConstants(cpIdx);
                    Object jStr = StringPool.jString(clazz.loader(), goStr);
                    staticVars.setRef(slotId, jStr);
                    break;

这里增加了字符串类型静态常量的初始化逻辑,代码比较简单,就不多解释了。至此,字符串相关的工作都做完了,下面进行测试。

六、测试字符串

修改startJVM()函数。改动非常小,只是在调用interpret()函数时,把传递给Java主方法的参数传递给它,代码如下:

 private static void startJVM(Cmd cmd) {
        Classpath classpath = new Classpath(cmd.jre, cmd.classpath);
        ClassLoader classLoader = new ClassLoader(classpath);
        //获取className
        String className = cmd.getMainClass().replace(".", "/");
        Class mainClass = classLoader.loadClass(className);
        Method mainMethod = mainClass.getMainMethod();
        if (null == mainMethod) {
            throw new RuntimeException("Main method not found in class " + cmd.getMainClass());
        }
        new Interpret(mainMethod, cmd.verboseClassFlag, cmd.args);
    }

打开interpreter类文件,修改interpret()函数,改动如下:

Interpret(Method method, boolean logInst, String args) {
        Thread thread = new Thread();
        Frame frame = thread.newFrame(method);
        thread.pushFrame(frame);

        if (null != args){
            Object jArgs = createArgsArray(method.clazz().loader(), args.split(" "));
            frame.localVars().setRef(0, jArgs);
        }

        loop(thread, logInst);
    }

最后,打开instructions\references\invokevirtual类,修改_println()函数,让它可以打印字符串,改动如下:

 case "(Ljava/lang/String;)V":
                Object jStr = stack.popRef();
                String goStr = StringPool.goString(jStr);
                System.out.println(goStr);
                break;

gostring()函数在stringPool类文件中,代码如下:

public static String goString(Object jStr) {
        Object charArr = jStr.getRefVar("value", "[C");
        return new String(charArr.chars());
    }

我们来执行一个稍微复杂的程序:

public class HelloWorld {

    public static void main(String[] args) {
        for (String str : args) {
            System.out.println(str);
        }
    }
}

输出结果如下:

"C:\Program Files\Java\jdk1.8.0_281\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.3\lib\idea_rt.jar=55718:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_281\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_281\jre\lib\rt.jar;D:\JavaProject\Idea-project\itstack-demo-jvm-master\itstack-demo-jvm-08\target\classes;D:\JAVA\maven\repository\com\beust\jcommander\1.72\jcommander-1.72.jar;D:\JAVA\maven\repository\org\projectlombok\lombok\1.18.0\lombok-1.18.0.jar;D:\JAVA\maven\repository\com\alibaba\fastjson\1.2.40\fastjson-1.2.40.jar" org.itstack.demo.jvm.Main -Xjre "C:\Program Files\Java\jdk1.8.0_281\jre" D:\JavaProject\Idea-project\itstack-demo-jvm-master\itstack-demo-jvm-08\src\test\java\org\itstack\demo\test\HelloWorld -verbose true -args 你好,java版虚拟机v1.0,欢迎你的到来。
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0x2a -> ALOAD_0 => 局部变量表:[{"num":0},{"num":0}] 操作数栈:[{"num":0},{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0x4c -> ASTORE_1 => 局部变量表:[{"num":0,"ref":{}},{"num":0}] 操作数栈:[{"num":0,"ref":{}},{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0x2b -> ALOAD_1 => 局部变量表:[{"num":0},{"num":0}] 操作数栈:[{"num":0},{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0xbe -> ARRAY_LENGTH => 局部变量表:[{"num":0,"ref":{}},{"num":0}] 操作数栈:[{"num":0,"ref":{}},{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0x3d -> ISTORE_2 => 局部变量表:[{"num":1},{"num":0}] 操作数栈:[{"num":1},{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0x03 -> ICONST_0 => 局部变量表:[{"num":1},{"num":0}] 操作数栈:[{"num":1},{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0x3e -> ISTORE_3 => 局部变量表:[{"num":0},{"num":0}] 操作数栈:[{"num":0},{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0x1d -> ILOAD_3 => 局部变量表:[{"num":0},{"num":0}] 操作数栈:[{"num":0},{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0x1c -> ILOAD_2 => 局部变量表:[{"num":0},{"num":0}] 操作数栈:[{"num":0},{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0xa2 -> IF_ICMPGE => 局部变量表:[{"num":0},{"num":1}] 操作数栈:[{"num":0},{"num":1}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0x2b -> ALOAD_1 => 局部变量表:[{"num":0},{"num":1}] 操作数栈:[{"num":0},{"num":1}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0x1d -> ILOAD_3 => 局部变量表:[{"num":0,"ref":{}},{"num":1}] 操作数栈:[{"num":0,"ref":{}},{"num":1}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0x32 -> AALOAD => 局部变量表:[{"num":0,"ref":{}},{"num":0}] 操作数栈:[{"num":0,"ref":{}},{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0x3a -> ASTORE => 局部变量表:[{"num":0,"ref":{}},{"num":0}] 操作数栈:[{"num":0,"ref":{}},{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0xb2 -> GET_STATIC => 局部变量表:[{"num":0},{"num":0}] 操作数栈:[{"num":0},{"num":0}]
java/lang/Object.<clinit>() 	寄存器(指令)0xb8 -> INVOKE_STATIC => 局部变量表:null 操作数栈:null
java/lang/Object.<clinit>() 	寄存器(指令)0xb1 -> RETURN => 局部变量表:null 操作数栈:null
java/lang/System.<clinit>() 	寄存器(指令)0xb8 -> INVOKE_STATIC => 局部变量表:[{"num":0}] 操作数栈:[{"num":0}]
java/lang/System.<clinit>() 	寄存器(指令)0x01 -> ACONST_NULL => 局部变量表:[{"num":0}] 操作数栈:[{"num":0}]
java/lang/System.<clinit>() 	寄存器(指令)0xb3 -> PUT_STATIC => 局部变量表:[{"num":0}] 操作数栈:[{"num":0}]
java/lang/System.<clinit>() 	寄存器(指令)0x01 -> ACONST_NULL => 局部变量表:[{"num":0}] 操作数栈:[{"num":0}]
java/lang/System.<clinit>() 	寄存器(指令)0xb3 -> PUT_STATIC => 局部变量表:[{"num":0}] 操作数栈:[{"num":0}]
java/lang/System.<clinit>() 	寄存器(指令)0x01 -> ACONST_NULL => 局部变量表:[{"num":0}] 操作数栈:[{"num":0}]
java/lang/System.<clinit>() 	寄存器(指令)0xb3 -> PUT_STATIC => 局部变量表:[{"num":0}] 操作数栈:[{"num":0}]
java/lang/System.<clinit>() 	寄存器(指令)0x01 -> ACONST_NULL => 局部变量表:[{"num":0}] 操作数栈:[{"num":0}]
java/lang/System.<clinit>() 	寄存器(指令)0xb3 -> PUT_STATIC => 局部变量表:[{"num":0}] 操作数栈:[{"num":0}]
java/lang/System.<clinit>() 	寄存器(指令)0x01 -> ACONST_NULL => 局部变量表:[{"num":0}] 操作数栈:[{"num":0}]
java/lang/System.<clinit>() 	寄存器(指令)0xb3 -> PUT_STATIC => 局部变量表:[{"num":0}] 操作数栈:[{"num":0}]
java/lang/System.<clinit>() 	寄存器(指令)0xb1 -> RETURN => 局部变量表:[{"num":0}] 操作数栈:[{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0xb2 -> GET_STATIC => 局部变量表:[{"num":0},{"num":0}] 操作数栈:[{"num":0},{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0x19 -> ALOAD => 局部变量表:[{"num":0},{"num":0}] 操作数栈:[{"num":0},{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0xb6 -> INVOKE_VIRTUAL => 局部变量表:[{"num":0},{"num":0,"ref":{}}] 操作数栈:[{"num":0},{"num":0,"ref":{}}]

你好,java版虚拟机v1.0,欢迎你的到来。

org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0x84 -> IINC => 局部变量表:[{"num":0},{"num":0}] 操作数栈:[{"num":0},{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0xa7 -> GOTO => 局部变量表:[{"num":0},{"num":0}] 操作数栈:[{"num":0},{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0x1d -> ILOAD_3 => 局部变量表:[{"num":0},{"num":0}] 操作数栈:[{"num":0},{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0x1c -> ILOAD_2 => 局部变量表:[{"num":1},{"num":0}] 操作数栈:[{"num":1},{"num":0}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0xa2 -> IF_ICMPGE => 局部变量表:[{"num":1},{"num":1}] 操作数栈:[{"num":1},{"num":1}]
org/itstack/demo/test/HelloWorld.main() 	寄存器(指令)0xb1 -> RETURN => 局部变量表:[{"num":1},{"num":1}] 操作数栈:[{"num":1},{"num":1}]


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值