前言 當我們開始學習Python時,我們會養成一些不良編碼習慣,而更可怕的是我們連自己也不知道。 我們學習變成的過程中,大概有會這樣的經歷: 寫的代碼只能完成了一次工作,但後來再執行就會報錯或者失敗,令人感到懊惱, 或者偶然發現一個內置函數可以讓你的工作更輕鬆時,瞬間豁然開朗。 我們中的大多數人仍然 ...
博客內容來源於 劉欣老師的課程,劉欣老師的公眾號 碼農翻身
博客內容來源於 Java虛擬機規範(JavaSE7)
博客內容的源碼 https://gitee.com/zumengjie/litejvm
閱讀此博客請配合源碼食用。
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); } }View Code
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"); } }View Code
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; } }View Code
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); } }View Code
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 **/