项目链接

https://github.com/FranzHaidnor/haidnorJVM

haidnorJVM

使用 Java17 编写的 Java 虚拟机

意义

  1. 纸上得来终觉浅,绝知此事要躬行。只学习 JVM 机制和理论,很多时候任然觉得缺乏那种大彻大悟之感
  2. 使用简单的方式实现 JVM,用于学习理解 JVM 运行原理

主要技术选型

实现功能

  • 实现了 99% 的 JVM 字节码指令。参照 JVM 字节码规范实现 The Java Virtual Machine Instruction Set
  • 支持算数运算符 (+,-,*,^,%,++,--)
  • 支持关系运算符 (==,!=,>,<,>=,<=)
  • 支持位运算符 (&,|,^,~,<<,>>,>>>)
  • 支持赋值运算符 (=,+=,-=,*=,%=,<<=,>>=,&=,^=,|=)
  • 支持 instanceof 运算符
  • 支持循环结构代码 (while,do...while,for,foreach)
  • 支持条件结构代码 (if,if...else,if...else if)
  • 支出创建自定义类
  • 支持创建对象、访问对象
  • 支持抽象类
  • 支持多态继承、接口
  • 支持访问静态方法
  • 支持访问对象方法
  • 支持 JDK 中自带的 Java 类
  • 支持反射
  • 支持异常
  • 枚举 (开发中...)
  • switch 语法 (开发中...)
  • lambda 表达式 (开发中...)

局限性

  • 不支持多线程
  • 不支持多维数组
  • 暂无双亲委派机制实现
  • 无垃圾收集器实现。垃圾回收依靠宿主 JVM

快速体验

你需要准备什么

  1. 集成开发环境 (IDE)。你可以选择包括 IntelliJ IDEA、Visual Studio Code 或 Eclipse 等等
  2. JDK 17。并配置 JAVA_HOME
  3. JDK 8。haidnorJVM 的主要目标是运行 Java8 本版的字节码文件。(但 haidnorJVM 没有强制要求字节码文件是 Java8 版本)
  4. Maven

配置 haidnorJVM

配置日志输出级别

resources\simplelogger.properties 文件中修改日志输出级别,一般使用 debuginfo

  • 配置 info 级别将不会看到任何 haidnorJVM 内部运行信息
  • 配置 debug 级别下运行将会非常友好的输出 JVM 正在执行的栈信息
public class Demo5 {

    public static void main(String[] args) {
String str = method1("hello world");
method1(str);
} public static String method1(String s) {
return method2(s);
} public static String method2(String s) {
return method3(s);
} public static String method3(String s) {
System.out.println(s);
return "你好 世界";
} }

每一个 结构图形,都表示一个 JVM 线程栈中的栈帧

配置 rt.jar 路径

修改 haidnorJVM.properties 文件中的内容。配置 rt.jar 的绝对路径,例如rt.jar=D:/Program Files/Java/jdk1.8.0_361/jre/lib/rt.jar

运行单元测试用例

在 IDE 中打开项目中 test 目录下的 haidnor.jvm.test.TestJVM.java 文件。 这是 haidnorJVM 的主要测试类, 里面的测试方法可以解析加载运行 .class 字节码文件。

public class TestJVM {
/**
* haidnorJVM 会加载 HelloWorld.java 在 target 目录下的编译后的字节码文件,然后运行其中的 `main(String[] args)` 方法。
* 你可以使用打断点的方式看到 haidnorJVM 是如何解释运行 Java 字节码的。
* 值得注意的是,这种方式编译运行的字节码文件是基于 java17 版本的。
*/
@Test
public void test() {
runMainClass(HelloWorld.class);
}
}

运行 .class 文件

  1. 使用 maven 命令将 haidnorJVM 编译打包,得到 haidnorJVM.jar 文件
  2. 编写一个简单的程序,例如以下代码
public class HelloWorld {
public static void main(String[] args) {
System.out.println("HelloWorld");
}
}
  1. 编译代码,得到 HelloWorld.class 文件 (推荐使用 JDK8 进行编译)
  2. 使用 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 文件的绝对路径

存在的问题

由于 haidnorJVM 目前运行 JDK 自带的类是使用反射解决的,因此 haidnorJVM 使用 JDK17 运行部分 JDK 自带的类时会存在一些问题,例如运行以下代码将会抛出异常

public class Demo {
public static void main(String[] args) {
List<Integer> 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 绕过访问性限制

联系作者



微信号: haidnor

如何用 Java 写一个 Java 虚拟机的更多相关文章

  1. 自己写一个java.lang.reflect.Proxy代理的实现

    前言 Java设计模式9:代理模式一文中,讲到了动态代理,动态代理里面用到了一个类就是java.lang.reflect.Proxy,这个类是根据代理内容为传入的接口生成代理用的.本文就自己写一个Pr ...

  2. 用JAVA写一个函数,功能例如以下: 随意给定一组数, 找出随意数相加之后的结果为35(随意设定)的情况

    用JAVA写一个函数.功能例如以下:随意给定一组数,比如{12,60,-8,99,15,35,17,18},找出随意数相加之后的结果为35(随意设定)的情况. 能够递归算法来解: package te ...

  3. 五:用JAVA写一个阿里云VPC Open API调用程序

    用JAVA写一个阿里云VPC Open API调用程序 摘要:用JAVA拼出来Open API的URL 引言 VPC提供了丰富的API接口,让网络工程是可以通过API调用的方式管理网络资源.用程序和软 ...

  4. 用java写一个servlet,可以将放在tomcat项目根目录下的文件进行下载

    用java写一个servlet,可以将放在tomcat项目根目录下的文件进行下载,将一个完整的项目进行展示,主要有以下几个部分: 1.servlet部分   Export 2.工具类:TxtFileU ...

  5. 用JAVA写一个多线程程序,写四个线程,其中二个对一个变量加1,另外二个对一个变量减1

    package com.ljn.base; /** * @author lijinnan * @date:2013-9-12 上午9:55:32 */ public class IncDecThrea ...

  6. 使用JAVA写一个简单的日历

    JAVA写一个简单的日历import java.text.DateFormat;import java.text.ParseException;import java.text.SimpleDateF ...

  7. 用Java写一个分布式缓存——缓存淘汰算法

    前言 之前也用过一些缓存中间件,框架,也想着自己是不是也能用Java写一个出来,于是就有了这个想法,打算在写的过程中同步进行总结. 源码:weloe/Java-Distributed-Cache (g ...

  8. 用Java写一个分布式缓存——缓存管理

    前言 之前也用过一些缓存中间件,框架,也想着自己是不是也能用Java写一个出来,于是就有了这个想法,打算在写的过程中同步进行总结 源码:weloe/Java-Distributed-Cache (gi ...

  9. linux常用终端指令+如何用vim写一个c程序并运行

    在装好ubuntu之后今天学习了一些linux的一些基础知识: windows里面打开命令窗口是win+r,在linux系统里面,ctrl+alt+t打开终端,今天的一些指令都是围绕终端来说的 首先s ...

  10. 原来热加载如此简单,手动写一个 Java 热加载吧

    1. 什么是热加载 热加载是指可以在不重启服务的情况下让更改的代码生效,热加载可以显著的提升开发以及调试的效率,它是基于 Java 的类加载器实现的,但是由于热加载的不安全性,一般不会用于正式的生产环 ...

随机推荐

  1. 【踩坑】.NET异步方法不标记async,Task<int> 返回值 return default问题

    ​ 在.NET中,返回类型为 Task<T> 的方法并不一定要标记为 async.这是因为 async 关键字只是用来告诉编译器该方法中包含异步操作,并且可以使用 await 和其他异步特 ...

  2. OpenFec介绍

    官网: http://openfec.org/accueil.html   1.提供的编解码器 Reed-Solomon stable codec over GF(28)                ...

  3. GDB使用简单总结

    简单总结常用gdb调试命令 不长篇讨论gdb是什么,或者怎么使用了,因为网上很多都讲的比较详细,以下只是做个备录,经常使用的命令,偶尔不用容易忘记! 1.set args xxxx  (xxx为参数) ...

  4. 【Docker】镜像管理

    一.搜索镜像 1.在官方网站搜索镜像 Docker 官方镜像仓库:https://hub.docker.com/ 2.docker search 搜索镜像 Usage: docker search [ ...

  5. SQL Server 2005递归查询

    WHIT XXX(列1,列2....) AS ( SELECT 列1,列2... FROM 表WHERE ID=xxxxxx UNION ALL SELECT 列1,列2.... FROM 表 WHE ...

  6. 2022-06-19:给出n个数字,你可以任选其中一些数字相乘,相乘之后得到的新数字x, x的价值是x的不同质因子的数量。 返回所有选择数字的方案中,得到的x的价值之和。 来自携程。

    2022-06-19:给出n个数字,你可以任选其中一些数字相乘,相乘之后得到的新数字x, x的价值是x的不同质因子的数量. 返回所有选择数字的方案中,得到的x的价值之和. 来自携程. 答案2022-0 ...

  7. 2022-02-23:如何搭建k8s单机环境(用k3s),并且搭建dashboard?

    2022-02-23:如何搭建k8s单机环境(用k3s),并且搭建dashboard? 答案2022-02-03: 使用场景:个人电脑.需要安装虚拟机,操作系统是centos. 个人电脑上测试,不需要 ...

  8. 3 分钟利用 FastGPT 和 Laf 将 ChatGPT 接入企业微信

    原文链接:https://forum.laf.run/d/556 FastGPT 是一个超级的 ChatGPT 平台项目,功能非常强大: 集成了 ChatGPT.GPT4 和 Claude 可以使用任 ...

  9. 使用umi+dva做一个demo

    最初只是使用react 进行开发项目,发现项目过大状态管理起来就相当困难,虽然有redux, mobx,但是使用起来还是相当繁琐,而目前umi有现成的轮子使用简单,当然愿意尝试了,趁现在假期有时间简单 ...

  10. awk 内置变量与自定义变量

    点击上方" 生信科技爱好者 ",马上关注 真爱,请置顶或星标 作者:ghostwu 原文:https://www.cnblogs.com/ghostwu/p/9085653.htm ...