How to write a Java virtual machine in Java

significance

  1. What I have learned on paper is ultimately shallow, and I know that I have to do it in detail. I only learn the JVM mechanism and theory, but many times I still feel that I lack that sense of enlightenment.
  2. Use a simple way to implement JVM to learn and understand the operating principles of JVM

Main technology selection

Implement function

  • Implemented 99% of JVM bytecode instructions. Implement The Java Virtual Machine Instruction Set with reference to JVM bytecode specifications 
  • Supports arithmetic operators ( +, -, *, ^, %, ++, --)
  • Supports relational operators ( ==, !=, >, <, >=, <=)
  • Supports bitwise operators ( &, |, ^, ~, <<, >>, >>>)
  • Support assignment operators ( =, +=, -=, *=, %=, <<=, >>=, &=, ^=, |=)
  • Support instanceof operator
  • Support loop structure code ( while, do...while, for, foreach)
  • Support conditional structure code ( if, if...else, if...else if)
  • ExpenditureCreate custom class
  • Support creating objects and accessing objects
  • Support abstract classes
  • Support polymorphic inheritance and interfaces
  • Support access to static methods
  • Support access object methods
  • Support Java classes that come with JDK
  • Support reflection
  • Support exceptions
  • Enumeration (under development...)
  • switch syntax (under development...)
  • lambda expression (under development...)

limitation

  • Does not support multi-threading
  • Multidimensional arrays are not supported
  • There is currently no parent delegation mechanism implemented.
  • No garbage collector implementation. Garbage collection relies on the host JVM

Quick experience

What do you need to prepare

  1. Integrated development environment (IDE). You can choose to include IntelliJ IDEA, Visual Studio Code or Eclipse, etc.
  2. JDK 17. and configure JAVA_HOME
  3. JDK8. The main goal of haidnorJVM is to run Java8 version bytecode files. (But haidnorJVM does not force the bytecode file to be Java8 version)
  4. Maven

Configure haidnorJVM

Configure log output level

To modify the log output level in  resources\simplelogger.properties the file, generally use  debug,info

  • Configuring the info level will not see any haidnorJVM internal running information
  • Configuring running at the debug level will output the stack information of the JVM being executed in a very friendly manner.
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 "你好 世界";
    }
    
}

Each   structure graph represents a stack frame in a JVM thread stack.

Configure rt.jar path

Modify  haidnorJVM.properties the contents of the file. Configure the absolute path of rt.jar, for examplert.jar=D:/Program Files/Java/jdk1.8.0_361/jre/lib/rt.jar

Run unit test cases

Open the file in the test directory of the project in the IDE  haidnor.jvm.test.TestJVM.java . This is the main test class of haidnorJVM. The test methods inside can parse, load and run .class bytecode files.

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

Run .class file

  1. Use the maven command to compile and package haidnorJVM and get  haidnorJVM.jar the file
  2. Write a simple program such as the following code
public class HelloWorld {
   public static void main(String[] args) {
     System.out.println("HelloWorld");
   }
}
  1. Compile the code and get the HelloWorld.class file (it is recommended to use JDK8 for compilation)
  2. Use haidnorJVM to run the program. Execute the command  java -jar haidnorJVM.jar -class R:\HelloWorld.class. Note! The absolute path of the class file is required

Run the .jar file

  1. Use the maven command to compile and package haidnorJVM and get  haidnorJVM.jar the file
  2. Write a java project, compile and package it into a .jar file, such as demo.jar. It is required that the META-INF/MANIFEST.MF file in the .jar file has  Main-Class attributes (containing  public static void main(String[] args) the main class information of the method)
  3. Use haidnorJVM to run the program. Execute the command  java -jar haidnorJVM.jar -class R:\demo.jar. Note! The absolute path of the jar file is required

Problems

Since haidnorJVM currently uses reflection to run the classes that come with the JDK, there will be some problems when haidnorJVM uses JDK17 to run some of the classes that come with the JDK. For example, running the following code will throw an exception.

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)

It indicates an attempt to access a method or field through reflection, but accessibility restrictions on the method or field prevent access.

This limitation is usually caused by the Java module system. The module system allows dividing code into independent modules
and controlling access permissions between modules. The reason for the above exception is that module java.base does not "opens java.util" to unnamed module, which means that the java.base module does not open the java.util package to the unnamed module.

Solution:
Add JVM parameters when starting haidnorJVM  --add-opens java.base/java.util=ALL-UNNAMED to bypass accessibility restrictions

Guess you like

Origin blog.csdn.net/2301_78834737/article/details/132004536