案例介绍
本案例通过java代码实现jvm规范中指令集和解释器,完成后就可以开始执行1到100的加和计算。
Java虚拟机顾名思义,就是一台虚拟的机器,而字节码(bytecode)就是运行在这台虚拟机器上的机器码。我们已经知道,每一个类或者接口都会被Java编译器编译成一个class文件,类或接口的方法信息就放在class文件的method_info结构中。如果方法不是抽象的,也不是本地方法,方法的Java代码就会被编译器编译成字节码(即使方法是空的,编译器也会生成一条return语句),存在method_info结构的Code属性中。
环境准备
1、jdk 1.8.0
2、IntelliJ IDEA Community Edition 2018.3.1 x64
配置信息
1、调试配置
2.1、配置位置:Run/Debug Configurations -> program arguments
2.2、配置内容:-Xjre "C:\Program Files\Java\jdk1.8.0_161\jre" E:\itstack\git\istack-demo\itstack-demo-jvm\itstack-demo-jvm-06\target\test-classes\org\itstack\demo\test\HelloWorld
代码示例
itstack-demo-jvm-06 ├── pom.xml └── src └── main │ └── java │ └── org.itstack.demo.jvm │ ├── classfile │ │ ├── attributes {BootstrapMethods/Code/ConstantValue...} │ │ ├── constantpool {CONSTANT_TAG_CLASS/CONSTANT_TAG_FIELDREF/CONSTANT_TAG_METHODREF...} │ │ ├── ClassFile.java │ │ ├── ClassReader.java │ │ └── MemberInfo.java │ ├── classpath │ │ ├── impl │ │ │ ├── CompositeEntry.java │ │ │ ├── DirEntry.java │ │ │ ├── WildcardEntry.java │ │ │ └── ZipEntry.java │ │ ├── Classpath.java │ │ └── Entry.java │ ├── classpath │ │ ├── base │ │ ├── comparisons │ │ ├── constants │ │ ├── control │ │ ├── conversions │ │ ├── extended │ │ ├── loads │ │ ├── math │ │ │ ├── add │ │ │ ├── and │ │ │ ├── div │ │ │ ├── iinc │ │ │ ├── mul │ │ │ ├── neg │ │ │ ├── or │ │ │ ├── rem │ │ │ ├── sh │ │ │ ├── sub │ │ │ └── xor │ │ ├── stack │ │ ├── store │ │ └── Factory │ ├── rtda │ │ ├── heap │ │ │ ├── constantpool │ │ │ ├── methodarea │ │ │ │ ├── Class.java │ │ │ │ ├── ClassMember.java │ │ │ │ ├── Field.java │ │ │ │ ├── Method.java │ │ │ │ ├── Object.java │ │ │ │ └── Slots.java │ │ │ └── ClassLoader.java │ │ ├── Frame.java │ │ ├── JvmStack.java │ │ ├── LocalVars.java │ │ ├── OperandStack.java │ │ ├── Slot.java │ │ └── Thread.java │ ├── Cmd.java │ ├── Interpret.java │ └── Main.java └── test └── java └── org.itstack.demo.test └── HelloWorld.java
Class.java
public class Class { public int accessFlags; public String name; public String superClassName; public String[] interfaceNames; public RunTimeConstantPool runTimeConstantPool; public Field[] fields; public Method[] methods; public ClassLoader loader; public Class superClass; public Class[] interfaces; public int instanceSlotCount; public int staticSlotCount; public Slots staticVars; public Class(ClassFile classFile) { this.accessFlags = classFile.accessFlags(); this.name = classFile.className(); this.superClassName = classFile.superClassName(); this.interfaceNames = classFile.interfaceNames(); this.runTimeConstantPool = new RunTimeConstantPool(this, classFile.constantPool()); this.fields = new Field().newFields(this, classFile.fields()); this.methods = new Method().newMethods(this, classFile.methods()); } public boolean isPublic() { return 0 != (this.accessFlags & AccessFlags.ACC_PUBLIC); } public boolean isFinal() { return 0 != (this.accessFlags & AccessFlags.ACC_FINAL); } public boolean isSuper() { return 0 != (this.accessFlags & AccessFlags.ACC_SUPER); } public boolean isInterface() { return 0 != (this.accessFlags & AccessFlags.ACC_INTERFACE); } public boolean isAbstract() { return 0 != (this.accessFlags & AccessFlags.ACC_ABSTRACT); } public boolean isSynthetic() { return 0 != (this.accessFlags & AccessFlags.ACC_SYNTHETIC); } public boolean isAnnotation() { return 0 != (this.accessFlags & AccessFlags.ACC_ANNOTATION); } public boolean isEnum() { return 0 != (this.accessFlags & AccessFlags.ACC_ENUM); } public RunTimeConstantPool constantPool() { return this.runTimeConstantPool; } public Slots staticVars() { return this.staticVars; } public boolean isAccessibleTo(Class other) { return this.isPublic() || this.getPackageName().equals(other.getPackageName()); } public String getPackageName() { int i = this.name.lastIndexOf("/"); if (i >= 0) return this.name; return ""; } public Method getMainMethod() { return this.getStaticMethod("main", "([Ljava/lang/String;)V"); } private Method getStaticMethod(String name, String descriptor) { for (Method method : this.methods) { if (method.name.equals(name) && method.descriptor.equals(descriptor)) { return method; } } return null; } public Object newObject() { return new Object(this); } public boolean isAssignableFrom(Class other) { if (this == other) return true; if (!other.isInterface()) { return this.isSubClassOf(other); } else { return this.isImplements(other); } } public boolean isSubClassOf(Class other) { for (Class c = this.superClass; c != null; c = c.superClass) { if (c == other) { return true; } } return false; } private boolean isImplements(Class other) { for (Class c = this; c != null; c = c.superClass) { for (Class clazz : c.interfaces) { if (clazz == other || clazz.isSubInterfaceOf(other)) { return true; } } } return false; } public boolean isSubInterfaceOf(Class iface) { for (Class superInterface : this.interfaces) { if (superInterface == iface || superInterface.isSubInterfaceOf(iface)) { return true; } } return false; } }
ClassMember.java
public class ClassMember { public int accessFlags; public String name; public String descriptor; public Class clazz; public void copyMemberInfo(MemberInfo memberInfo) { this.accessFlags = memberInfo.accessFlags(); this.name = memberInfo.name(); this.descriptor = memberInfo.descriptor(); } public boolean isPublic() { return 0 != (this.accessFlags & AccessFlags.ACC_PUBLIC); } public boolean isPrivate() { return 0 != (this.accessFlags & AccessFlags.ACC_PRIVATE); } public boolean isProtected() { return 0 != (this.accessFlags & AccessFlags.ACC_PROTECTED); } public boolean isStatic() { return 0 != (this.accessFlags & AccessFlags.ACC_STATIC); } public boolean isFinal() { return 0 != (this.accessFlags & AccessFlags.ACC_FINAL); } public boolean isSynthetic() { return 0 != (this.accessFlags & AccessFlags.ACC_SYNTHETIC); } public String name() { return this.name; } public String descriptor() { return this.descriptor; } public Class clazz() { return this.clazz; } public boolean isAccessibleTo(Class d) { if (this.isPublic()) { return true; } Class c = this.clazz; if (this.isProtected()) { return d == c || c.getPackageName().equals(d.getPackageName()); } if (!this.isPrivate()) { return c.getPackageName().equals(d.getPackageName()); } return d == c; } }
Field.java
package org.itstack.demo.jvm.rtda.heap.methodarea; import org.itstack.demo.jvm.classfile.MemberInfo; import org.itstack.demo.jvm.classfile.attributes.impl.ConstantValueAttribute; import org.itstack.demo.jvm.rtda.heap.constantpool.AccessFlags; public class Field extends ClassMember { public int constValueIndex; public int slotId; public Field[] newFields(Class clazz, MemberInfo[] cfFields) { Field[] fields = new Field[cfFields.length]; for (int i = 0; i < cfFields.length; i++) { fields[i] = new Field(); fields[i].clazz = clazz; fields[i].copyMemberInfo(cfFields[i]); fields[i].copyAttributes(cfFields[i]); } return fields; } public void copyAttributes(MemberInfo cfField) { ConstantValueAttribute valAttr = cfField.ConstantValueAttribute(); if (null != valAttr) { this.constValueIndex = valAttr.constantValueIdx(); } } public boolean isVolatile() { return 0 != (this.accessFlags & AccessFlags.ACC_VOLATILE); } public boolean isTransient() { return 0 != (this.accessFlags & AccessFlags.ACC_TRANSIENT); } public boolean isEnum() { return 0 != (this.accessFlags & AccessFlags.ACC_ENUM); } public int constValueIndex() { return this.constValueIndex; } public int slotId() { return this.slotId; } public boolean isLongOrDouble() { return this.descriptor.equals("J") || this.descriptor.equals("D"); } }
Method.java
package org.itstack.demo.jvm.rtda.heap.methodarea; import org.itstack.demo.jvm.classfile.MemberInfo; import org.itstack.demo.jvm.classfile.attributes.impl.CodeAttribute; import org.itstack.demo.jvm.rtda.heap.constantpool.AccessFlags; public class Method extends ClassMember { public int maxStack; public int maxLocals; public byte[] code; public Method[] newMethods(Class clazz, MemberInfo[] cfMethods) { Method[] methods = new Method[cfMethods.length]; for (int i = 0; i < cfMethods.length; i++) { methods[i] = new Method(); methods[i].clazz = clazz; methods[i].copyMemberInfo(cfMethods[i]); methods[i].copyAttributes(cfMethods[i]); } return methods; } public void copyAttributes(MemberInfo cfMethod) { CodeAttribute codeAttr = cfMethod.codeAttribute(); if (null != codeAttr) { this.maxStack = codeAttr.maxStack(); this.maxLocals = codeAttr.maxLocals(); this.code = codeAttr.data(); } } public boolean isSynchronized() { return 0 != (this.accessFlags & AccessFlags.ACC_SYNCHRONIZED); } public boolean isBridge() { return 0 != (this.accessFlags & AccessFlags.ACC_BRIDGE); } public boolean isVarargs() { return 0 != (this.accessFlags & AccessFlags.ACC_VARARGS); } public boolean isNative() { return 0 != (this.accessFlags & AccessFlags.ACC_NATIVE); } public boolean isAbstract() { return 0 != (this.accessFlags & AccessFlags.ACC_ABSTRACT); } public boolean isStrict() { return 0 != (this.accessFlags & AccessFlags.ACC_STRICT); } public int maxStack() { return this.maxStack; } public int maxLocals() { return this.maxLocals; } public byte[] code() { return this.code; } }
Object.java
package org.itstack.demo.jvm.rtda.heap.methodarea; public class Object { Class clazz; Slots fields; public Object(Class clazz){ this.clazz = clazz; this.fields = new Slots(clazz.instanceSlotCount); } public Class clazz(){ return this.clazz; } public Slots fields(){ return this.fields; } public boolean isInstanceOf(Class clazz){ return clazz.isAssignableFrom(this.clazz); } }
Slots.java
package org.itstack.demo.jvm.rtda.heap.methodarea; import org.itstack.demo.jvm.rtda.Slot; public class Slots { private Slot[] slots; public Slots(int slotCount) { if (slotCount > 0) { slots = new Slot[slotCount]; for (int i = 0; i < slotCount; i++) { slots[i] = new Slot(); } } } public void setInt(int idx, int val) { this.slots[idx].num = val; } public int getInt(int idx) { return this.slots[idx].num; } public void setFloat(int idx, float val) { this.slots[idx].num = (int) val; } public float getFloat(int idx) { return this.slots[idx].num; } public void setLong(int idx, long val) { this.slots[idx].num = (int) val; this.slots[idx + 1].num = (int) (val >> 32); } public long getLong(int idx) { int low = this.slots[idx].num; int high = this.slots[idx + 1].num; return (long) high << 32 | (long) low; } public void setDouble(int idx, double val) { this.setLong(idx, (long) val); } public Double getDouble(int idx) { return (double) this.getLong(idx); } public void setRef(int idx, Object ref) { this.slots[idx].ref = ref; } public Object getRef(int idx){ return this.slots[idx].ref; } }
测试结果
{可以看到已经输出;5050}
"C:\Program Files\Java\jdk1.8.0_161\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2018.3.1\lib\idea_rt.jar=61887:D:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2018.3.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_161\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_161\jre\lib\rt.jar;E:\itstack\git\istack-demo\itstack-demo-jvm\itstack-demo-jvm-05\target\classes;D:\Program Files (x86)\apache-maven-2.2.1\repository\com\beust\jcommander\1.72\jcommander-1.72.jar;D:\Program Files (x86)\apache-maven-2.2.1\repository\org\projectlombok\lombok\1.18.0\lombok-1.18.0.jar;D:\Program Files (x86)\apache-maven-2.2.1\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_161\jre" E:\itstack\git\istack-demo\itstack-demo-jvm\itstack-demo-jvm-05\target\test-classes\org\itstack\demo\test\HelloWorld classpath:org.itstack.demo.jvm.classpath.Classpath@4bf558aa class:E:\itstack\git\istack-demo\itstack-demo-jvm\itstack-demo-jvm-05\target\test-classes\org\itstack\demo\test\HelloWorld args:null 寄存器(指令):0x03 -> ICONST_0 => 局部变量表:[{"num":0},{"num":0}] 操作数栈:[{"num":0},{"num":0}] 寄存器(指令):0x3c -> ISTORE_1 => 局部变量表:[{"num":0},{"num":0}] 操作数栈:[{"num":0},{"num":0}] 寄存器(指令):0x04 -> ICONST_1 => 局部变量表:[{"num":0},{"num":0}] 操作数栈:[{"num":0},{"num":0}] 寄存器(指令):0x3d -> ISTORE_2 => 局部变量表:[{"num":1},{"num":0}] 操作数栈:[{"num":1},{"num":0}] 寄存器(指令):0x1c -> ILOAD_2 => 局部变量表:[{"num":1},{"num":0}] 操作数栈:[{"num":1},{"num":0}] 寄存器(指令):0x10 -> BIPUSH => 局部变量表:[{"num":1},{"num":0}] 操作数栈:[{"num":1},{"num":0}] 寄存器(指令):0xa3 -> IF_ICMPGT => 局部变量表:[{"num":1},{"num":100}] 操作数栈:[{"num":1},{"num":100}] 寄存器(指令):0x1b -> ILOAD_1 => 局部变量表:[{"num":1},{"num":100}] 操作数栈:[{"num":1},{"num":100}] 寄存器(指令):0x1c -> ILOAD_2 => 局部变量表:[{"num":0},{"num":100}] 操作数栈:[{"num":0},{"num":100}] ... ... 寄存器(指令):0x60 -> IADD => 局部变量表:[{"num":4950},{"num":100}] 操作数栈:[{"num":4950},{"num":100}] 寄存器(指令):0x3c -> ISTORE_1 => 局部变量表:[{"num":5050},{"num":100}] 操作数栈:[{"num":5050},{"num":100}] 寄存器(指令):0x84 -> IINC => 局部变量表:[{"num":5050},{"num":100}] 操作数栈:[{"num":5050},{"num":100}] 寄存器(指令):0xa7 -> GOTO => 局部变量表:[{"num":5050},{"num":100}] 操作数栈:[{"num":5050},{"num":100}] 寄存器(指令):0x1c -> ILOAD_2 => 局部变量表:[{"num":5050},{"num":100}] 操作数栈:[{"num":5050},{"num":100}] 寄存器(指令):0x10 -> BIPUSH => 局部变量表:[{"num":101},{"num":100}] 操作数栈:[{"num":101},{"num":100}] 寄存器(指令):0xa3 -> IF_ICMPGT => 局部变量表:[{"num":101},{"num":100}] 操作数栈:[{"num":101},{"num":100}] 5050 寄存器(指令):0xb1 -> RETURN => 局部变量表:[{"num":101},{"num":5050}] 操作数栈:[{"num":101},{"num":5050}] Exception in thread "main" java.lang.RuntimeException: jvm stack is empty! at org.itstack.demo.jvm.rtda.JvmStack.pop(JvmStack.java:33)