From 41a4d662ad822238f6a9ec8e83c719cb59e7c345 Mon Sep 17 00:00:00 2001 From: wangxiang <276644985haidnor@gmail.com> Date: Fri, 28 Jul 2023 10:57:21 +0800 Subject: [PATCH] update README.md --- README.md | 99 +++++++++++++------ .../jvm/instruction/comparisons/IFEQ.java | 4 +- .../jvm/instruction/comparisons/IFNE.java | 15 ++- src/test/java/haidnor/jvm/test/TestJVM.java | 5 +- .../java/haidnor/jvm/test/clazz/Human.java | 2 +- .../haidnor/jvm/test/clazz/StudentEnum.java | 23 +++++ .../java/haidnor/jvm/test/demo/Demo8.java | 2 +- .../haidnor/jvm/test/demo/demo_enum_1.java | 11 +++ .../haidnor/jvm/test/demo/demo_foreach_2.java | 7 +- .../haidnor/jvm/test/demo/demo_foreach_3.java | 8 +- 10 files changed, 134 insertions(+), 42 deletions(-) create mode 100644 src/test/java/haidnor/jvm/test/clazz/StudentEnum.java create mode 100644 src/test/java/haidnor/jvm/test/demo/demo_enum_1.java diff --git a/README.md b/README.md index dcc2369..ba0d584 100644 --- a/README.md +++ b/README.md @@ -11,25 +11,36 @@ * [Apache Commons CLI](https://commons.apache.org/proper/commons-cli/) # 实现功能 -* 支持基本数据类型数学运算 -* 支持循环、条件结构代码 -* switch 语法(开发中...) -* 支持创建对象,访问对象 +* 实现了 99% 的 JVM 字节码指令。参照 JVM 字节码规范实现 [The Java Virtual Machine Instruction Set](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html) +* 支持算数运算符 (`+`,`-`,`*`,`^`,`%`,`++`,`--`) +* 支持关系运算符 (`==`,`!=`,`>`,`<`,`>=`,`<=`) +* 支持位运算符 (`&`,`|`,`^`,`~`,`<<`,`>>`,`>>>`) +* 支持赋值运算符 (`=`,`+=`,`-=`,`*=`,`%=`,`<<=`,`>>=`,`&=`,`^=`,`|=`) +* 支持 instanceof 运算符 +* 支持循环结构代码 (`while`,`do...while`,`for`,`foreach`) +* 支持条件结构代码 (`if`,`if...else`,`if...else if`) +* 支出创建自定义类 +* 支持创建对象、访问对象 +* 支持抽象类 * 支持多态继承、接口 -* 支持反射 -* 支持 JDK 中自带的 Java 类 * 支持访问静态方法 +* 支持访问对象方法 +* 支持 JDK 中自带的 Java 类 +* 支持反射 * 支持异常 -* lambda 表达式(开发中...) +* 枚举 (开发中...) +* switch 语法 (开发中...) +* lambda 表达式 (开发中...) # 局限性 * 不支持多线程 * 不支持多维数组 -* 垃圾回收依靠宿主 JVM +* 暂无双亲委派机制实现 +* 无垃圾收集器实现。垃圾回收依靠宿主 JVM # 快速体验 ## 你需要准备什么 -1. 集成开发环境 (IDE)。你可以选择包括 IntelliJ IDEA、Spring Tools、Visual Studio Code 或 Eclipse 等等 +1. 集成开发环境 (IDE)。你可以选择包括 IntelliJ IDEA、Visual Studio Code 或 Eclipse 等等 2. JDK 17。并配置 JAVA_HOME 3. JDK 8。haidnorJVM 的主要目标是运行 Java8 本版的字节码文件。(但 haidnorJVM 没有强制要求字节码文件是 Java8 版本) 4. Maven @@ -39,7 +50,7 @@ 在 `resources\simplelogger.properties` 文件中修改日志输出级别,一般使用 `debug`、`info` * 配置 info 级别将不会看到任何 haidnorJVM 内部运行信息 -* 配置 debug 级别下运行将会非常友好的输出 JVM 正在执行的栈信息 (例如使用 haidnorJVM 运行以下代码) +* 配置 debug 级别下运行将会非常友好的输出 JVM 正在执行的栈信息 ```java public class Demo5 { @@ -63,42 +74,72 @@ public class Demo5 { } ``` +每一个 `匚` 结构图形,都表示一个 JVM 线程栈中的栈帧 ![](/readme/20230721204333.png) -### 配置 rt.jar +### 配置 rt.jar 路径 修改 `haidnorJVM.properties` 文件中的内容。配置 rt.jar 的绝对路径,例如`rt.jar=D:/Program Files/Java/jdk1.8.0_361/jre/lib/rt.jar` ## 运行单元测试用例 -打开 test 目录下的 `haidnor.jvm.test.TestJVM` 类文件。 这是 haidnorJVM 功能的主要测试类。 里面的测试方法可以解析并加载一些class字节码文件。 +在 IDE 中打开项目中 test 目录下的 `haidnor.jvm.test.TestJVM.java` 文件。 这是 haidnorJVM 的主要测试类, 里面的测试方法可以解析加载运行 .class 字节码文件。 ```java -@Test -public void test() throws Exception { - runMainClass(HelloWorld.class); +public class TestJVM { + /** + * haidnorJVM 会加载 HelloWorld.java 在 target 目录下的编译后的字节码文件,然后运行其中的 `main(String[] args)` 方法。 + * 你可以使用打断点的方式看到 haidnorJVM 是如何解释运行 Java 字节码的。 + * 值得注意的是,这种方式编译运行的字节码文件是基于 java17 版本的。 + */ + @Test + public void test() { + runMainClass(HelloWorld.class); + } } ``` -例如以上代码会加载 HelloWorld.class 类在 target 目录下的字节码文件,然后使用 haidnorJVM 运行其中的 main 函数。你可以使用打断点的方式看到 haidnorJVM 是如何解释运行 Java 字节码的。 -值得注意的是,这种方式编译运行的字节码文件是基于 java17 版本的。 ## 运行 .class 文件 1. 使用 maven 命令将 haidnorJVM 编译打包,得到 `haidnorJVM.jar` 文件 2. 编写一个简单的程序,例如以下代码 - ```java - public class HelloWorld { - public static void main(String[] args) { - System.out.println("HelloWorld"); - } +```java +public class HelloWorld { + public static void main(String[] args) { + System.out.println("HelloWorld"); } - ``` -3. 编译代码,得到 HelloWorld.class 文件。(推荐使用 JDK8 进行编译) -4. 使用 haidnorJVM 运行程序。执行命令 `java -jar haidnorJVM.jar -class R:\HelloWorld.class`。注意需要 class 文件的绝对路径 +} +``` +3. 编译代码,得到 HelloWorld.class 文件 (推荐使用 JDK8 进行编译) +4. 使用 haidnorJVM 运行程序。执行命令 `java -jar haidnorJVM.jar -class R:\HelloWorld.class`。注意! 需要 class 文件的绝对路径 ## 运行 .jar 文件 1. 使用 maven 命令将 haidnorJVM 编译打包,得到 `haidnorJVM.jar` 文件 -2. 编写一个 java 项目编译打包成 .jar 文件,例如 demo.jar。要求 .jar 文件中的 META-INF/MANIFEST.MF 文件内有 `Main-Class` 属性 (含有 public static void main(String[] args) 方法的主类信息) -3. 使用 haidnorJVM 运行程序。执行命令 `java -jar haidnorJVM.jar -class R:\demo.jar`。注意需要 jar 文件的绝对路径 +2. 编写一个 java 项目编译打包成 .jar 文件,例如 demo.jar。要求 .jar 文件中的 META-INF/MANIFEST.MF 文件内有 `Main-Class` 属性 (含有 `public static void main(String[] args)` 方法的主类信息) +3. 使用 haidnorJVM 运行程序。执行命令 `java -jar haidnorJVM.jar -class R:\demo.jar`。注意! 需要 jar 文件的绝对路径 -# 计划 -目前运行 JDK 自带的类的方案是使用反射解决,并存在一些bug,计划将来完全不依赖反射运行。 +# 存在的问题 +由于 haidnorJVM 目前运行 JDK 自带的类是使用反射解决的,因此 haidnorJVM 使用 JDK17 运行部分 JDK 自带的类时会存在一些问题,例如运行以下代码将会抛出异常 +```java +public class Demo { + public static void main(String[] args) { + List list = List.of(1, 2, 3, 4, 5); + list.add(6); + } +} +``` + +``` +java.lang.reflect.InaccessibleObjectException: Unable to make public boolean java.util.ImmutableCollections$AbstractImmutableCollection.add(java.lang.Object) accessible: module java.base does not "opens java.util" to unnamed module @18769467 + + at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354) + at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297) + at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199) + at java.base/java.lang.reflect.Method.setAccessible(Method.java:193) +``` +它表示尝试通过反射来访问一个方法或字段,但该方法或字段的可访问性限制导致无法访问。 + +这个限制通常是由于 Java 模块系统引起的。模块系统允许将代码划分为独立的模块, +并控制模块之间的访问权限。以上异常的原因是 module java.base does not "opens java.util" to unnamed module,也就是说 java.base 模块没有向未命名模块开放 java.util 包 + +**解决方法:** +启动 haidnorJVM 时添加 JVM 参数 `--add-opens java.base/java.util=ALL-UNNAMED` 绕过访问性限制 # 联系作者 ![](/readme/20230721181408.png ) diff --git a/src/main/java/haidnor/jvm/instruction/comparisons/IFEQ.java b/src/main/java/haidnor/jvm/instruction/comparisons/IFEQ.java index 2d0354f..3b8e3be 100644 --- a/src/main/java/haidnor/jvm/instruction/comparisons/IFEQ.java +++ b/src/main/java/haidnor/jvm/instruction/comparisons/IFEQ.java @@ -6,9 +6,7 @@ import haidnor.jvm.runtime.StackValue; import haidnor.jvm.util.CodeStream; public class IFEQ extends Instruction { - /** - * 下次再执行的偏移量 - */ + private final int offSet; public IFEQ(CodeStream codeStream) { diff --git a/src/main/java/haidnor/jvm/instruction/comparisons/IFNE.java b/src/main/java/haidnor/jvm/instruction/comparisons/IFNE.java index a803536..292b569 100644 --- a/src/main/java/haidnor/jvm/instruction/comparisons/IFNE.java +++ b/src/main/java/haidnor/jvm/instruction/comparisons/IFNE.java @@ -19,11 +19,20 @@ public class IFNE extends Instruction { @Override public void execute(Frame frame) { StackValue v1 = frame.pop(); - if ((int) v1.getValue() != 0) { - super.setOffSet(offSet); + if (v1.getValue() instanceof Boolean) { + if (((boolean) v1.getValue())) { + super.setOffSet(offSet); + } else { + super.setOffSet(3); + } } else { - super.setOffSet(3); + if ((int) v1.getValue() != 0) { + super.setOffSet(offSet); + } else { + super.setOffSet(3); + } } + } @Override diff --git a/src/test/java/haidnor/jvm/test/TestJVM.java b/src/test/java/haidnor/jvm/test/TestJVM.java index 7ce0f11..8b0c62b 100644 --- a/src/test/java/haidnor/jvm/test/TestJVM.java +++ b/src/test/java/haidnor/jvm/test/TestJVM.java @@ -30,7 +30,6 @@ public class TestJVM { Klass mainMeteKlass = bootClassLoader.loadClass(mainClass.getName().replace('.', '/')); KlassMethod mainKlassMethod = JavaClassUtil.getMainMethod(mainMeteKlass); Metaspace.registerJavaClass(mainMeteKlass); - JavaExecutionEngine.callMainMethod(mainKlassMethod); } @@ -148,6 +147,10 @@ public class TestJVM { runMainClass(demo_finally_3.class); } + @Test + public void demo_enum_1() throws Exception { + runMainClass(demo_enum_1.class); // TODO support enum + } @Test public void test_NEW() throws Exception { diff --git a/src/test/java/haidnor/jvm/test/clazz/Human.java b/src/test/java/haidnor/jvm/test/clazz/Human.java index aae5dcb..0bc3a54 100644 --- a/src/test/java/haidnor/jvm/test/clazz/Human.java +++ b/src/test/java/haidnor/jvm/test/clazz/Human.java @@ -1,6 +1,6 @@ package haidnor.jvm.test.clazz; -public class Human implements Organism { +public abstract class Human implements Organism { public static final String HUMAN_NAME = "123"; diff --git a/src/test/java/haidnor/jvm/test/clazz/StudentEnum.java b/src/test/java/haidnor/jvm/test/clazz/StudentEnum.java new file mode 100644 index 0000000..8996e49 --- /dev/null +++ b/src/test/java/haidnor/jvm/test/clazz/StudentEnum.java @@ -0,0 +1,23 @@ +package haidnor.jvm.test.clazz; + +public enum StudentEnum { + + ZHANG_SAN(1,"张三"); + + private int age; + private String name; + + StudentEnum(int age, String name) { + this.age = age; + this.name = name; + } + + public int getAge() { + return age; + } + + public String getName() { + return name; + } + +} diff --git a/src/test/java/haidnor/jvm/test/demo/Demo8.java b/src/test/java/haidnor/jvm/test/demo/Demo8.java index 1ce2ed1..3b13d12 100644 --- a/src/test/java/haidnor/jvm/test/demo/Demo8.java +++ b/src/test/java/haidnor/jvm/test/demo/Demo8.java @@ -8,7 +8,7 @@ import haidnor.jvm.test.clazz.Student; public class Demo8 { public static void main(String[] args) { - Human organism1 = new Human(); + Human organism1 = new Student(); Organism0 organism = new Student(); organism.die(); } diff --git a/src/test/java/haidnor/jvm/test/demo/demo_enum_1.java b/src/test/java/haidnor/jvm/test/demo/demo_enum_1.java new file mode 100644 index 0000000..a5472c5 --- /dev/null +++ b/src/test/java/haidnor/jvm/test/demo/demo_enum_1.java @@ -0,0 +1,11 @@ +package haidnor.jvm.test.demo; + +import haidnor.jvm.test.clazz.StudentEnum; + +public class demo_enum_1 { + public static void main(String[] args) { + StudentEnum studentEnum = StudentEnum.ZHANG_SAN; + System.out.println(studentEnum.getAge()); + System.out.println(studentEnum.getName()); + } +} diff --git a/src/test/java/haidnor/jvm/test/demo/demo_foreach_2.java b/src/test/java/haidnor/jvm/test/demo/demo_foreach_2.java index 002a36d..7c34984 100644 --- a/src/test/java/haidnor/jvm/test/demo/demo_foreach_2.java +++ b/src/test/java/haidnor/jvm/test/demo/demo_foreach_2.java @@ -1,6 +1,7 @@ package haidnor.jvm.test.demo; import java.util.ArrayList; +import java.util.List; /** * 需要添加启动参数 @@ -9,9 +10,13 @@ import java.util.ArrayList; public class demo_foreach_2 { public static void main(String[] args) { - ArrayList list = new ArrayList<>(); + List list = new ArrayList<>(); list.add(1); list.add(2); + for (int i = 0; i < 1000; i++) { + list.add(i); + } + for (Integer integer : list) { System.out.println(integer); } diff --git a/src/test/java/haidnor/jvm/test/demo/demo_foreach_3.java b/src/test/java/haidnor/jvm/test/demo/demo_foreach_3.java index 08742cf..95b5488 100644 --- a/src/test/java/haidnor/jvm/test/demo/demo_foreach_3.java +++ b/src/test/java/haidnor/jvm/test/demo/demo_foreach_3.java @@ -10,9 +10,11 @@ public class demo_foreach_3 { public static void main(String[] args) { List list = List.of(1, 2, 3, 4, 5); - for (Integer integer : list) { - System.out.println(integer); - } + list.add(6); + +// for (Integer integer : list) { +// System.out.println(integer); +// } } }