本章分为两部分,前半部分讨论数组和数组相关指令,后半部分讨论字符串。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}]