相關文章:
我們都知道 Java 類加載有這麼幾個階段:加載、驗證、準備、解析和初始化。本文就是實現加載階段的查找 class 文件。《自己動手寫 Java 虛擬機》前兩章其實並未涉及到很多 JVM 相關的知識,主要是在爲後面做準備,一些內容書上過於繁瑣,不屬於主幹內容,這裏會簡寫或者忽略。
類路徑
按照搜索的先後順序,類路徑可以 分爲以下3個部分:
- 啓動類路徑(bootstrap classpath)
- 擴展類路徑(extension classpath)
- 用戶類路徑(user classpath)
參考 java
命令,可以通過 -cp/-classpath
命令去指定目錄和 zip/jar 文件的類搜索路徑,從 Java6 開始支持通配符(*)。
增加命令參數
爲了方便,先在項目中增加了字符串操作的工具類:
/**
* @author Dongguabai
* @Description
* @Date 創建於 2020-06-11 06:12
*/
public class StringUtils {
public static boolean isBlank(final CharSequence cs) {
int strLen;
if (cs == null || (strLen = cs.length()) == 0) {
return true;
}
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace(cs.charAt(i))) {
return false;
}
}
return true;
}
public static boolean isNotBlank(final CharSequence cs) {
return !isBlank(cs);
}
private StringUtils(){}
}
在 DongguabaiJVMCommander
中定義一個內部類處理 -cp/-classpath
命令的參數:
public static class ClassPathParams {
@SubParameter(order = 0)
public String classPath;
@SubParameter(order = 1)
public String classFile;
public ClassPathParams() {
}
}
增加參數選項:
/**
* class load
*/
@Parameter(names = {"-cp", "--classpath"}, required = false, arity = 2, description = "class search path of directories and zip/jar files")
private ClassPathParams classPathParams;
查找 class 文件
這個其實也比較簡單,就是讀取一下 class 文件的內容。對於 Java 虛擬機來說,class 文件來源很多,這裏只實現從文件中獲取 class 文件。
定義一個工廠類:
/**
* @author Dongguabai
* @Description ClassLoader 工廠
* @Date 創建於 2020-06-11 06:21
*/
public class ClassLoaderFactory {
public static ClassLoader getClassLoader(String classPath){
//todo 生成 ClassLoader
return new DirClassLoader(classPath);
}
private ClassLoaderFactory() {
}
}
定義 ClassLoader 抽象類:
/**
* @author Dongguabai
* @Description ClassLoader 抽象類
* @Date 創建於 2020-06-11 06:21
*/
public abstract class ClassLoader {
private String classPath;
public abstract byte[] loadClass(String classFile);
ClassLoader(String classPath) {
this.classPath = classPath;
}
public String getClassPath() {
return classPath;
}
}
從目錄中加載 class:
/**
* @author Dongguabai
* @Description 解析目錄
* @Date 創建於 2020-06-11 06:03
*/
public class DirClassLoader extends ClassLoader {
public DirClassLoader(String classPath) {
super(classPath);
}
@Override
public byte[] loadClass(String classFile) {
System.out.printf("DirClassLoader:[%s]\n", classFile);
String filePath = getClassPath() + File.separator + classFile;
try {
return IOUtils.toByte(filePath);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
在 DongguabaiJVMCommander
中增加 loadClassPath
方法,輸出讀取到的 class 文件內容:
/**
* @author Dongguabai
* @Description
* @Date 創建於 2020-06-08 13:36
*/
public class DongguabaiJVMCommander {
private static final String CURRENT_VERSION = "Dongguabai jdk version \"0.0.1\"";
/**
* 查看版本
*/
@Parameter(names = {"--version", "-v"}, required = false, arity = 0, description = "print product version and exit")
private boolean version;
/**
* help
*/
@Parameter(names = {"--help", "-h"}, required = false, arity = 0, description = " print the help message")
private boolean help;
/**
* 運行 Java 代碼
*/
@Parameter(names = {"--run"}, required = false, arity = 1, description = "to execute a java file")
private String run;
/**
* class load
*/
@Parameter(names = {"-cp", "--classpath"}, required = false, arity = 2, description = "class search path of directories and zip/jar files")
private ClassPathParams classPathParams;
public static void main(String... argv) {
DongguabaiJVMCommander main = new DongguabaiJVMCommander();
JCommander jCommander = JCommander.newBuilder()
.addObject(main)
.build();
jCommander.parse(argv);
main.run(jCommander);
}
/**
* 運行
* todo 優化
*
* @param jCommander
*/
private void run(JCommander jCommander) {
if (version) {
System.out.println(CURRENT_VERSION);
return;
}
if (help) {
jCommander.usage();
return;
}
if (classPathParams != null && StringUtils.isNotBlank(classPathParams.classFile) && StringUtils.isNotBlank(classPathParams.classPath)) {
System.out.printf("load Class:%s %s\n", classPathParams.classPath, classPathParams.classFile);
loadClasspath();
}
if (StringUtils.isNotBlank(run)) {
System.out.printf("execute:%s", run);
}
}
private void loadClasspath() {
ClassLoader classLoader = ClassLoaderFactory.getClassLoader(classPathParams.classPath);
byte[] bytes = classLoader.loadClass(classPathParams.classFile);
String s = CommonUtils.bytesToHexString(bytes);
System.out.println(s);
}
public static class ClassPathParams {
@SubParameter(order = 0)
public String classPath;
@SubParameter(order = 1)
public String classFile;
public ClassPathParams() {
}
}
}
測試
寫一個簡單的類:
/**
* @author Dongguabai
* @Description
* @Date 創建於 2020-06-11 07:53
*/
public class UserV1 {
private String username;
private int age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
編譯後,增加啓動參數:
-cp /Users/dongguabai/IdeaProjects/dongguabai-jvm/target/classes/com/dongguabai/jvm/test UserV1.class
啓動:
load Class:/Users/dongguabai/IdeaProjects/dongguabai-jvm/target/classes/com/dongguabai/jvm/test UserV1.class
DirClassLoader:[UserV1.class]
ca fe ba be 00 00 00 31 00 20 0a 00 05 00 1b 09 00 04 00 1c 09 00 04 00 1d 07 00 1e 07 00 1f 01 00 08 75 73 65 72 6e 61 6d 65 01 00 12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 01 00 03 61 67 65 01 00 01 49 01 00 06 3c 69 6e 69 74 3e 01 00 03 28 29 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 01 00 12 4c 6f 63 61 6c 56 61 72 69 61 62 6c 65 54 61 62 6c 65 01 00 04 74 68 69 73 01 00 20 4c 63 6f 6d 2f 64 6f 6e 67 67 75 61 62 61 69 2f 6a 76 6d 2f 74 65 73 74 2f 55 73 65 72 56 31 3b 01 00 0b 67 65 74 55 73 65 72 6e 61 6d 65 01 00 14 28 29 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 01 00 0b 73 65 74 55 73 65 72 6e 61 6d 65 01 00 15 28 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 56 01 00 06 67 65 74 41 67 65 01 00 03 28 29 49 01 00 06 73 65 74 41 67 65 01 00 04 28 49 29 56 01 00 0a 53 6f 75 72 63 65 46 69 6c 65 01 00 0b 55 73 65 72 56 31 2e 6a 61 76 61 0c 00 0a 00 0b 0c 00 06 00 07 0c 00 08 00 09 01 00 1e 63 6f 6d 2f 64 6f 6e 67 67 75 61 62 61 69 2f 6a 76 6d 2f 74 65 73 74 2f 55 73 65 72 56 31 01 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 00 21 00 04 00 05 00 00 00 02 00 02 00 06 00 07 00 00 00 02 00 08 00 09 00 00 00 05 00 01 00 0a 00 0b 00 01 00 0c 00 00 00 2f 00 01 00 01 00 00 00 05 2a b7 00 01 b1 00 00 00 02 00 0d 00 00 00 06 00 01 00 00 00 08 00 0e 00 00 00 0c 00 01 00 00 00 05 00 0f 00 10 00 00 00 01 00 11 00 12 00 01 00 0c 00 00 00 2f 00 01 00 01 00 00 00 05 2a b4 00 02 b0 00 00 00 02 00 0d 00 00 00 06 00 01 00 00 00 0f 00 0e 00 00 00 0c 00 01 00 00 00 05 00 0f 00 10 00 00 00 01 00 13 00 14 00 01 00 0c 00 00 00 3e 00 02 00 02 00 00 00 06 2a 2b b5 00 02 b1 00 00 00 02 00 0d 00 00 00 0a 00 02 00 00 00 13 00 05 00 14 00 0e 00 00 00 16 00 02 00 00 00 06 00 0f 00 10 00 00 00 00 00 06 00 06 00 07 00 01 00 01 00 15 00 16 00 01 00 0c 00 00 00 2f 00 01 00 01 00 00 00 05 2a b4 00 03 ac 00 00 00 02 00 0d 00 00 00 06 00 01 00 00 00 17 00 0e 00 00 00 0c 00 01 00 00 00 05 00 0f 00 10 00 00 00 01 00 17 00 18 00 01 00 0c 00 00 00 3e 00 02 00 02 00 00 00 06 2a 1b b5 00 03 b1 00 00 00 02 00 0d 00 00 00 0a 00 02 00 00 00 1b 00 05 00 1c 00 0e 00 00 00 16 00 02 00 00 00 06 00 0f 00 10 00 00 00 00 00 06 00 08 00 09 00 01 00 01 00 19 00 00 00 02 00 1a
輸出了這麼一堆,有木有很熟悉,下篇文章開始分析、解析 class 文件。
代碼地址:https://github.com/dongguabai/mini-jvm
References
- 《自己動手寫 Java 虛擬機》
- http://jcommander.org/#_variable_arities
歡迎關注公衆號