相关文章:

我们都知道 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 虚拟机(二)-查找 Class 文件_加载