jvm造轮子
博客内容来源于 刘欣老师的课程,刘欣老师的公众号 码农翻身
博客内容来源于 Java虚拟机规范(JavaSE7)
博客内容的源码 https://gitee.com/zumengjie/litejvm
阅读此博客请配合源码食用。
ClassLoader
Class
MethodArea
ExecutorEngine
StackFrame
ExecutionResult
Heap
ClassLoader
ClassLoader就是根据类名去classpath路径上找到Class文件然后解析Class文件形成Class类对象。
package com.datang.litejvm.loader; import com.datang.litejvm.clz.Class; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import java.io.*; import java.util.ArrayList; import java.util.List; /** * @author: 顶风少年 * @Description: 类加载器 * @date: 13:54 2022/6/10 **/ public class ClassLoader { /** * @author: 顶风少年 * @Description: 存放环境变量 * @date: 21:58 2022/6/8 **/ private List<String> clzPaths = new ArrayList<String>(); /** * @author: 顶风少年 * @Description: 添加环境变量 classPath * @date: 21:53 2022/6/8 **/ public void addClassPath(String path) { if (this.clzPaths.contains(path)) { return; } this.clzPaths.add(path); } /** * @author: 顶风少年 * @Description: 返回classPath, 多个中间使用 ; 拼接 * @date: 21:53 2022/6/8 **/ public String getClassPath() { return StringUtils.join(this.clzPaths, ";"); } /** * @author: 顶风少年 * @Description: 将classpath和className拼接获取类路径 * @date: 21:53 2022/6/8 **/ public byte[] readBinaryCode(String className) { className = className.replace('.', File.separatorChar) + ".class"; for (String path : this.clzPaths) { String clzFileName = path + File.separatorChar + className; byte[] codes = loadClassFile(clzFileName); if (codes != null) { return codes; } } return null; } /** * @author: 顶风少年 * @Description: 根据类路径读取class文件 * @date: 21:54 2022/6/8 **/ private byte[] loadClassFile(String clzFileName) { File f = new File(clzFileName); try { return IOUtils.toByteArray(new FileInputStream(f)); } catch (IOException e) { e.printStackTrace(); return null; } } /** * @author: 顶风少年 * @Description: 解析class文件形成Class对象,在这里是形成ClassFile对象 * @date: 13:55 2022/6/10 **/ public Class loadClass(String className) { byte[] codes = this.readBinaryCode(className); ClassParser parser = new ClassParser(); return parser.parse(codes); } }
Class
首先我们对一个已经编译完成的class文件进行解析,解析的过程就是读取每一个字节,然后了解其结构以及含义,将其解析为一个Class类。使用javap -v xxx.class命令可以查看该class的结构。
Class字节码文件的内容十分紧凑,首先是魔数,小版本号,大版本号,常量池,类访问权限,类,父类,父接口,字段列表,方法列表。常量池占了大部分的字节码文件,其余的部分很多都会引用常量池项。
解析Class文件就是读取固定长度的字节,魔数为4个字节,小版本号2个字节,大版本号两个字节。接下来的常量池稍微复杂,两个字节标记了常量池项的总个数,其中每一个常量池项都有对应的数据结构。需要先读取1个字节判断它的结构,例如tag是1则它是一个CONSTANT_Utf8_info,这是一个最简单的结构,2个字节的长度表示后续字符串的长度,然后再读取对应长度的字节数。常量池项是可以引用常量池项的,例如第7项是一个CONSTANT_Class_info这个结构包含了2个字节的index它指向常量池的第40项目,是Class对应的类名。解析常量池时需要根据tag判断常量池项是什么结构,然后再根据具体的结构读取字节。
接下来是读取2个字节类的访问权限,无论是类,字段,方法都有访问权限,注意访问权限可能有多个,例如类可以是 <public> <final>
接下来是2个字节的类名,2个字节的父类名,2个字节的接口个数,每个接口名也是2个字节。
之后是类成员变量也就是字段,2个字节的成员个数,每个字段里包含2个字节的访问权限,2个字节的字段名,2个字节的字段类型,两个字节的的属性个数,这个属性也是有多种结构的,方法里也有,放到方法里说。
字节码文件的最后一部分是方法,2个字节的方法个数,每个方法包含2个字节的访问权限,2个字节的方法名,2个字节的方法签名(入参和返回值)。2个字节的属性个数,每个属性中包含,2个字节的属性名称。在方法中只有一个属性就是Code。
jvm定义的属性有以下结构,字段的属性也在其中。
首先是2个字节的属性名称,根据属性名判断属性。每个属性都有4个字节的属性长度,它标记了接下来的属性内容的字节数。Code属性中包含2个字节的栈深度,2个字节的局部变量表,4个字节的字节码长度,字节码长度为N则表示有N个字节的字节码指令,每个指令1个字节。对于字节码指令需要展开说,每个字节码指令根据其含义的不同可能带有不同的参数,例如bb含义为new对象,bb的后边有2个字节是它的参数,这两个参数指向了常量池中的常量项是需要new的对象类名。
在字节码指令后是2个字节的异常长度,异常的相关的内容,我本次没有解析,只是手动跳过了异常。
Code属性中还包含了2个其他属性。LineNumberTable和LocalVariableTable其一是行号相关,其二是参数列表。
2个字节区分属性的名称,4个字节表示属性内容的长度。LineNumberTable有2个字节的行数,每一行中包含2个字节的起始位,2个字节的行号。
LocalVariableTable有2个字节的参数个数,每个参数有2个字节的起始位,2个字节的长度,2个字节的属性名,2个字节的下标。
以上是Class文件中可以解析出来的内容,当然根据Class的复杂程度,解析出来的内容不同,我这里是最基本的Class属性,将其解析完毕后,形成一个Class类。
package com.datang.litejvm.clz; import com.datang.litejvm.constant.ConstantClassInfo; import com.datang.litejvm.constant.ConstantPool; import com.datang.litejvm.constant.ConstantUTF8Info; import java.util.Iterator; import java.util.List; /** * @author: 顶风少年 * @Description: Class类 * @date: 13:55 2022/6/10 **/ public class Class { //魔数 private String magic; //小版本号 private int minorVersion; //大版本号 private int majorVersion; //常量池 private ConstantPool pool; //访问标记 private ClassAccessFlag classAccessFlag; //类 private int thisClassIndex; //父类 private int superClassIndex; private String superClassName; //接口引用 private List<Integer> interfaceIndexList; //属性列表 private List<Field> fieldList; //方法 private List<Method> methodList; //------------------------------------------------------- /** * @author: 顶风少年 * @Description: 魔数 * @date: 11:39 2022/6/10 **/ public String getMagic() { return magic; } /** * @author: 顶风少年 * @Description: 魔数 * @date: 11:39 2022/6/10 **/ public void setMagic(String magic) { this.magic = magic; } /** * @author: 顶风少年 * @Description: 小版本号 * @date: 11:28 2022/6/10 **/ public int getMinorVersion() { return minorVersion; } /** * @author: 顶风少年 * @Description: 小版本号 * @date: 11:28 2022/6/10 **/ public void setMinorVersion(int minorVersion) { this.minorVersion = minorVersion; } /** * @author: 顶风少年 * @Description: 大版本号 * @date: 11:28 2022/6/10 **/ public int getMajorVersion() { return majorVersion; } /** * @author: 顶风少年 * @Description: 大版本号 * @date: 11:28 2022/6/10 **/ public void setMajorVersion(int majorVersion) { this.majorVersion = majorVersion; } /** * @author: 顶风少年 * @Description: 常量池 * @date: 11:28 2022/6/10 **/ public ConstantPool getConstantPool() { return pool; } /** * @author: 顶风少年 * @Description: 常量池 * @date: 11:28 2022/6/10 **/ public void setConstPool(ConstantPool pool) { this.pool = pool; } /** * @author: 顶风少年 * @Description: 访问标记 * @date: 17:18 2022/6/10 **/ public ClassAccessFlag getAccessFlag() { return classAccessFlag; } /** * @author: 顶风少年 * @Description: 访问标记 * @date: 17:18 2022/6/10 **/ public void setAccessFlag(ClassAccessFlag classAccessFlag) { this.classAccessFlag = classAccessFlag; } /** * @author: 顶风少年 * @Description: 类 * @date: 17:18 2022/6/10 **/ public int getThisClassIndex() { return thisClassIndex; } public void setThisClassIndex(int thisClassIndex) { this.thisClassIndex = thisClassIndex; } /** * @author: 顶风少年 * @Description: 父类 * @date: 17:18 2022/6/10 **/ public int getSuperClassIndex() { return superClassIndex; } public void setSuperClassIndex(int superClassIndex) { this.superClassIndex = superClassIndex; ConstantClassInfo constantClassInfo = (ConstantClassInfo) pool.getConstantInfo(superClassIndex); this.superClassName = constantClassInfo.getClassName(); } public String getSuperClassName() { return superClassName; } /** * @author: 顶风少年 * @Description: 接口 * @date: 17:18 2022/6/10 **/ public List<Integer> getInterfaceIndexList() { return interfaceIndexList; } public void setInterfaceIndexList(List<Integer> interfaceIndexList) { this.interfaceIndexList = interfaceIndexList; } /** * @author: 顶风少年 * @Description: 属性列表 * @date: 11:22 2022/6/12 **/ public List<Field> getFieldList() { return fieldList; } /** * @author: 顶风少年 * @Description: 属性列表 * @date: 11:22 2022/6/12 **/ public void setFieldList(List<Field> fieldList) { this.fieldList = fieldList; } /** * @author: 顶风少年 * @Description: 方法 * @date: 18:31 2022/6/12 **/ public List<Method> getMethodList() { return methodList; } /** * @author: 顶风少年 * @Description: 方法 * @date: 18:31 2022/6/12 **/ public void setMethodList(List<Method> methodList) { this.methodList = methodList; } /** * @author: 顶风少年 * @Description: 根据方法名和方法签名查询方法 * @date: 10:47 2022/6/16 **/ public Method getMethod(String methodName, String paramAndResultType) { Method rMethod = null; Iterator<Method> iter = methodList.iterator(); while (iter.hasNext()) { Method method = iter.next(); int nameIndex = method.getNameIndex(); int descriptorIndex = method.getDescriptorIndex(); ConstantUTF8Info nameInfo = (ConstantUTF8Info) pool.getConstantInfo(nameIndex); ConstantUTF8Info descriptorInfo = (ConstantUTF8Info) pool.getConstantInfo(descriptorIndex); if (nameInfo.getBytes().equals(methodName) && descriptorInfo.getBytes().equals(paramAndResultType)) { rMethod = method; } } return rMethod; } /** * @author: 顶风少年 * @Description: 查询main方法 * @date: 10:36 2022/6/16 **/ public Method getMainMethod() { return getMethod("main", "([Ljava/lang/String;)V"); } }
MethodArea
方法区中有个Map它的key是class类名,value是Class对象。使用ClassLoader解析后的Class对象都存在这里。
package com.datang.litejvm.engin; import com.datang.litejvm.clz.Field; import com.datang.litejvm.clz.Method; import com.datang.litejvm.constant.ConstantFieldRefInfo; import com.datang.litejvm.constant.ConstantMethodRefInfo; import com.datang.litejvm.loader.ClassLoader; import com.datang.litejvm.clz.Class; import java.util.HashMap; import java.util.Map; /** * @author: 顶风少年 * @Description: 方法区 * @date: 10:49 2022/6/16 **/ public class MethodArea { public static final MethodArea instance = new MethodArea(); /** * 注意:我们做了极大的简化, ClassLoader 只有一个, 实际JVM中的ClassLoader,是一个双亲委托的模型 */ private ClassLoader classLoader = null; /** * @author: 顶风少年 * @Description: 存放所有的Class * @date: 10:39 2022/6/16 **/ Map<String, Class> map = new HashMap<String, Class>(); private MethodArea(){ } /** * @author: 顶风少年 * @Description: 单例,获取常量池 * @date: 10:39 2022/6/16 **/ public static MethodArea getInstance(){ return instance; } public void setClassFileLoader(ClassLoader clzLoader){ this.classLoader = clzLoader; } /** * @author: 顶风少年 * @Description: 获取main方法 * @date: 10:39 2022/6/16 **/ public Method getMainMethod(String className){ Class clz = this.findClass(className); return clz.getMainMethod(); } /** * @author: 顶风少年 * @Description: 从指定class中 根据名称获取方法 * @date: 22:23 2022/6/16 **/ public Method getMethod(ConstantMethodRefInfo constantMethodRefInfo){ Class aClass = findClass(constantMethodRefInfo.getClassName()); Method method = aClass.getMethod(constantMethodRefInfo.getMethodName(), constantMethodRefInfo.getParamAndResult()); return method; } /** * @author: 顶风少年 * @Description: 创建Class * @date: 10:38 2022/6/16 **/ public Class findClass(String className){ if(map.get(className) != null){ return map.get(className); } // 看来该class 文件还没有load过 Class aClass = this.classLoader.loadClass(className); map.put(className, aClass); return aClass; } }
ExecutorEngine
执行引擎中主要有一个栈结构,栈中的每个元素是StackFrame栈帧。执行引擎执行第一步将main方法压入栈中,然后就是执行栈帧。每个栈帧其实就是一个method当方法执行结束后返回ExecutionResult里边封装了该方法是暂存运行其他method还是方法运行结束出栈。如果是运行其他的method则将跳转方法压入栈中,并且传递参数,如果是方法执行结束则将当前方法出栈。执行引擎就是在不断的判断栈内是否还有元素,如果栈为空则表示当前程序执行结束。
package com.datang.litejvm.engin; import com.datang.litejvm.clz.Method; import java.util.ArrayList; import java.util.List; import java.util.Stack; /** * @author: 顶风少年 * @Description: 执行引擎 * @date: 15:19 2022/6/16 **/ public class ExecutorEngine { /** * @author: 顶风少年 * @Description: 栈 * @date: 15:41 2022/6/16 **/ private Stack<StackFrame> stack = new Stack<StackFrame>(); public void execute(Method mainMethod) { //创建main栈帧 StackFrame stackFrame = StackFrame.create(mainMethod); //入栈 stack.push(stackFrame); //接下来就是对栈做操作了,入栈,出栈 while (!stack.isEmpty()) { //拿到栈顶栈帧 StackFrame frame = stack.peek(); //执行栈帧 ExecutionResult result = frame.execute(); //暂停,并运行新的栈帧.有函数调用了 if (result.isPauseAndRunNewFrame()) { //下一个method Method nextMethod = result.getNextMethod(); //形成新的栈帧 StackFrame nextFrame = StackFrame.create(nextMethod); nextFrame.setCallerFrame(frame); setupFunctionCallParams(frame, nextFrame); //将新的栈帧也入栈 stack.push(nextFrame); } else { //出栈 stack.pop(); } } } /** * @author: 顶风少年 * @Description: 给下个调用方法设置参数 * @date: 16:07 2022/6/16 **/ private void setupFunctionCallParams(StackFrame currentFrame, StackFrame nextFrame) { Method nextMethod = nextFrame.getMethod(); //获取参数列表 List<String> parameterList = nextMethod.getParameterList(); List<JavaObject> values = new ArrayList<>(); //要添加 this int paramNum = parameterList.size() + 1; while (paramNum > 0) { values.add(currentFrame.getOperandStack().pop()); paramNum--; } List<JavaObject> params = new ArrayList<>(); for (int i = values.size() - 1; i >= 0; i--) { params.add(values.get(i)); } //设置局部变量表 nextFrame.setLocalVariableTable(params); } }
StackFrame
每个方法入栈都会形成一个StackFrame,栈帧中包含两个数据结构。局部变量表和操作数栈,局部变量表是用来存放方法的入参,方法内的计算结果,操作数栈则是真正运算的地方,我们经常说的 1 + 2 = 3 的操作其实是在操作数栈进行的,先将 1 和 2 压入操作数栈,将 1 和 2 出栈进行运算最后得出的 3 将其再次压入操作数栈。StackFrame执行时就是从method总取出Code属性中的操作指令,一条一条的执行。每条指令执行结束后会对ExecutionResult进行设置。
package com.datang.litejvm.engin; import com.datang.litejvm.clz.Method; import com.datang.litejvm.cmd.ByteCodeCommand; import java.util.ArrayList; import java.util.List; import java.util.Stack; /** * @author: 顶风少年 * @Description: 函数栈帧 * @date: 15:41 2022/6/16 **/ public class StackFrame { //局部变量表 private List<JavaObject> localVariableTable = new ArrayList<JavaObject>(); //操作数栈 private Stack<JavaObject> operandStack = new Stack<JavaObject>(); //字节码指令偏移量,指向下一个操作指令 int index = 0; //当前方法 private Method m = null; //上一个函数栈帧 private StackFrame callerFrame = null; private StackFrame(Method m) { this.m = m; } //创建函数栈帧 public static StackFrame create(Method m) { StackFrame frame = new StackFrame(m); return frame; } /** * @author: 顶风少年 * @Description: 上一个函数栈帧 * @date: 16:44 2022/6/16 **/ public StackFrame getCallerFrame() { return callerFrame; } public void setCallerFrame(StackFrame callerFrame) { this.callerFrame = callerFrame; } /** * @author: 顶风少年 * @Description: 栈帧所属方法 * @date: 16:45 2022/6/16 **/ public Method getMethod() { return m; } /** * @author: 顶风少年 * @Description: 设置局部变量表 * @date: 16:40 2022/6/16 **/ public void setLocalVariableTable(List<JavaObject> values) { this.localVariableTable = values; } /** * @author: 顶风少年 * @Description: 获取局部变量表中的某个变量 * @date: 10:22 2022/6/17 **/ public JavaObject getLocalVariableValue(int index) { return this.localVariableTable.get(index); } /** * @author: 顶风少年 * @Description: 向局部变量表设置值 * @date: 10:38 2022/6/17 **/ public void setLocalVariableValue(int index, JavaObject jo) { //问题: 为什么要这么做?? if (this.localVariableTable.size() - 1 < index) { for (int i = this.localVariableTable.size(); i <= index; i++) { this.localVariableTable.add(null); } } this.localVariableTable.set(index, jo); } /** * @author: 顶风少年 * @Description: 执行方法, 就是执行字节码指令 * @date: 17:11 2022/6/16 **/ public ExecutionResult execute() { List<ByteCodeCommand> cmds = m.getCodeAttr().getCmds(); while (index < cmds.size()) { //执行结果 ExecutionResult result = new ExecutionResult(); //下一条字节码指令 ByteCodeCommand cmd = cmds.get(index); System.out.println(cmd.toString()); //执行 cmd.execute(this, result); //运行下一条 if (result.isRunNextCmd()) { index++; } else if (result.isExitCurrentFrame()) { //退出当前栈帧,return 剩余的栈帧不执行了 return result; } else if (result.isPauseAndRunNewFrame()) { //暂停当前栈帧,执行新的函数栈帧 index++; return result; } else if (result.isJump()) { //跳转指令,跳转到下一个字节码指令 int offset = result.getNextCmdOffset(); //设置下一个指令的偏移量 this.index = getNextCommandIndex(offset); } else { index++; } } //如果循环走完了,说明没有任何的跳转,停止,表示当前StackFrame的指令全部执行完毕,可以退出了 ExecutionResult result = new ExecutionResult(); result.setNextAction(ExecutionResult.EXIT_CURRENT_FRAME); return result; } /** * @author: 顶风少年 * @Description: 根据偏移量查询字节码指令 * @date: 17:48 2022/6/16 **/ public int getNextCommandIndex(int offset) { List<ByteCodeCommand> cmds = m.getCodeAttr().getCmds(); for (int i = 0; i < cmds.size(); i++) { if (cmds.get(i).getOffset() == offset) { return i; } } throw new RuntimeException("Can't find next command"); } /** * @author: 顶风少年 * @Description: 获取操作数栈 * @date: 19:04 2022/6/16 **/ public Stack<JavaObject> getOperandStack() { return this.operandStack; } }
ExecutionResult
操作指令执行结束后会设置ExecutionResult,这个类标记了当前字节码执行后的行为,默认的是RUN_NEXT_CMD继续执行下一条字节码指令,也可能是JUMP跳转到指定的指令号,EXIT_CURRENT_FRAME停止执行,PAUSE_AND_RUN_NEW_FRAME暂存当前栈帧,执行新的栈帧。如果是JUMP或RUN_NEXT_CMD则当前栈帧继续执行。如果是EXIT_CURRENT_FRAME和PAUSE_AND_RUN_NEW_FRAME就要返回到执行引擎层。
package com.datang.litejvm.engin; import com.datang.litejvm.clz.Method; /** * @author: 顶风少年 * @Description: 执行结果 * @date: 17:29 2022/6/16 **/ public class ExecutionResult { //默认值 执行下一条指令 public static final int RUN_NEXT_CMD = 1; //跳转指令 public static final int JUMP = 2; //退出当前栈帧,剩余指令不执行了 public static final int EXIT_CURRENT_FRAME = 3; //暂停当前栈帧,执行新的函数栈帧 public static final int PAUSE_AND_RUN_NEW_FRAME = 4; //下一次的行为 private int nextAction = RUN_NEXT_CMD; //如果是跳转指令 JUMP ,则会记录下一个指令偏移量 private int nextCmdOffset = 0; //如果是执行新的栈帧 EXIT_CURRENT_FRAME 则需要设置栈帧对应的方法 private Method nextMethod; /** * @author: 顶风少年 * @Description: 获取下一个执行的方法 * @date: 17:46 2022/6/16 **/ public Method getNextMethod() { return nextMethod; } public void setNextMethod(Method nextMethod) { this.nextMethod = nextMethod; } /** * @author: 顶风少年 * @Description: 设置字节码执行后的行为, 默认为 RUN_NEXT_CMD 执行下一条指令 * @date: 17:31 2022/6/16 **/ public void setNextAction(int action) { this.nextAction = action; } /** * @author: 顶风少年 * @Description: 下一个字节码指令偏移量 * @date: 17:43 2022/6/16 **/ public int getNextCmdOffset() { return nextCmdOffset; } public void setNextCmdOffset(int nextCmdOffset) { this.nextCmdOffset = nextCmdOffset; } /** * @author: 顶风少年 * @Description: 执行结果 * @date: 17:44 2022/6/16 **/ public boolean isPauseAndRunNewFrame() { return this.nextAction == PAUSE_AND_RUN_NEW_FRAME; } public boolean isExitCurrentFrame() { return this.nextAction == EXIT_CURRENT_FRAME; } public boolean isRunNextCmd() { return this.nextAction == RUN_NEXT_CMD; } public boolean isJump() { return this.nextAction == JUMP; } }
Heap
堆这个对象用来模拟创建不同类型的对象,当前可以创建String,int,float,Object。如果创建的Object则还可以给Object设置属性。
package com.datang.litejvm.engin; /** * @author: 顶风少年 * @Description: 堆,可以有多种类型,对象,字符串,int float * @date: 21:02 2022/6/16 **/ public class Heap { /** * 没有实现垃圾回收, 所以对于下面新创建的对象, 并没有记录到一个数据结构当中 */ private static Heap instance = new Heap(); private Heap() { } public static Heap getInstance(){ return instance; } public JavaObject newObject(String clzName){ JavaObject jo = new JavaObject(JavaObject.OBJECT); jo.setClassName(clzName); return jo; } public JavaObject newString(String value){ JavaObject jo = new JavaObject(JavaObject.STRING); jo.setStringValue(value); return jo; } public JavaObject newFloat(float value){ JavaObject jo = new JavaObject(JavaObject.FLOAT); jo.setFloatValue(value); return jo; } public JavaObject newInt(int value){ JavaObject jo = new JavaObject(JavaObject.INT); jo.setIntValue(value); return jo; } }