[开源JVM] yvm - 自制Java虚拟机

YVM是用C++写的一个Java虚拟机,现在支持Java大部分功能,以及一个基于标记清除算法的并发垃圾回收器. 不过还有很多bug等待修复。 感兴趣的朋友pull request/fork/star吧。

Github repo

https://github.com/racaljk/yvm

已支持语言特性

高级特性逐步支持中,可以开Issue提议或者直接PR

构建和运行

  • 先决条件
    • Boost(>=1.65) 请在CMakeLists.txt中手动配置Boost库位置
    • CMake(>=3.5)
    • C++14
    • gcc/msvc/mingw均可
  • 老生常谈
$ cd yvm
$ cmake .
$ make -j4
$ make test
$ ./yvm --help
Usage:
    --help                List help documentations and usages.
    --runtime arg         Attach java runtime libraries where yvm would lookup 
                        classes at
    --run arg             Program which would be executed soon
You must specify the "runtime" flag to tell yvm where it could find jdk classes, and also program name is required.
$ ./yvm --runtime=C:\Users\Cthulhu\Desktop\yvm\bytecode ydk.test.QuickSort

运行效果

  • helloworld
  • 快速排序
  • and more see its github repository readme.md...

开发文档

1. 从字节码到对象

MethodArea负责管理字节码到JavaClass的完整生命周期。MethodArea的方法是自解释的:

class MethodArea {
public:
    // 方法区需要从运行时目录中搜索相关的*.class文件
    MethodArea(const vector<string>& libPaths);
    ~MethodArea();

    // 查看一个类是否存在
    JavaClass* findJavaClass(const string& jcName);
    //加载jcName类
    bool loadJavaClass(const string& jcName);
    //移除jcName(该方法用于垃圾回收器)
    bool removeJavaClass(const string& jcName);
    //链接jcName类,初始化static字段
    void linkJavaClass(const string& jcName);
    //初始化jcName,初始化静态字段,调用static{}
    void initJavaClass(CodeExecution& exec, const string& jcName);

public:
    //辅助方法,如果不存在jcName则加载 
    JavaClass* loadClassIfAbsent(const string& jcName);
    //如果未链接jcName则链接
    void linkClassIfAbsent(const string& jcName);
    //如果未初始化jcName则初始化
    void initClassIfAbsent(CodeExecution& exec, const string& jcName);
}

假设磁盘存在一个Test.class文件,它会经历如下过程:

Test.class[磁盘中]-> loadJavaClass("Test.class")[内存中] -> linkJavaClass("Test.class")->initJavaClass("Test.class")

现在虚拟机就可以使用这个JavaClass创建对应的对象了:

// yrt 是全局运行时对象,ma表示方法区模块,jheap表示堆模块
JavaClass* testClass = yrt.ma->findJavaClass("Test.class");
JObject* testInstance = yrt.jheap->createObject(*testClass);

2.1 对象内部构造

虚拟机执行时栈上存放的都是JObject,它的结构如下:

struct JObject {
    std::size_t offset = 0; 
    const JavaClass* jc{}; 
};

offset唯一代表一个对象,所有在堆上面的操作都需要这个offset。jc指向对象的Class表示。 堆中的对象是按照<offset,fields>方式进行存放的:

[1]  ->  [field_a, field_b, field_c]
[2]  ->  []
[3]  ->  [field_a,field_b]
[4]  ->  [field_a]
[..] ->  [...]

只要我们持有offset,就可以查找/添加/删除对应的field

数组几乎和上面类似,只是多了长度,少了Class指针

struct JArray {
    int length = 0;
    std::size_t offset = 0; 
};
[1]  ->   <3, [field_a, field_b, field_c]>
[2]  ->   <0, []>
[3]  ->   <2, [field_a,field_b]>
[4]  ->   <1, [field_a]>
[..] ->   <..,[...]>

2.2 从对象创建到消亡

上面提到,对象持有一个offset和jc,其中jc表示的JavaClass是由MethodArea负责管理的,offset则是由JavaHeap负责管理。JavaHeap提供了大量API,这里选取的是最重要的:

class JavaHeap {
public:
    //创建对象和数组
    JObject* createObject(const JavaClass& javaClass);
    JArray* createObjectArray(const JavaClass& jc, int length);

    //获取对象字段
    auto getFieldByName(const JavaClass* jc, const string& name,
                        const string& descriptor, JObject* object);
    //设置对象字段
    void putFieldByName(const JavaClass* jc, const string& name,
                        const string& descriptor, JObject* object,
                        JType* value);
    //设置数组元素
    void putElement(const JArray& array, size_t index, JType* value);
    //获取数组元素
    auto getElement(const JArray& array, size_t index);
    
    //移除对象和数组
    void removeArray(size_t offset;
    void removeObject(size_t offset);
};

还是Test.class那个例子,假设对应的Test.java构造如下:

public class Test{
    public int k;
    private String hello;
}

在第一步我们已经获取到了Test类在虚拟机中的类表示以及对象表示,现在就可以对类的字段进行操作了:

const JavaClass* testClass = yrt.ma->findJavaClass("Test.class");
JObject* testInstance = yrt.jheap->createObject(*testClass);
//获取hello字段
JObject*  helloField = yrt.jheap->getFieldByName(testClass,"hello","Ljava/lang/String;",testInstance);
//设置k字段
yrt.jheap->putFieldByName(testClass,"k","I",testInstance);

Ⅰ. 关于JDK

部分JDK类是JVM运行攸关的,但由于JDK比较复杂不便于初期开发,所以这里用重写过的JDK代替,源码参见javaclass目录,可以使用compilejava.bat进行编译,编译后*.class文件位于bytecode. 目前重写过的JDK类有:

  • java.lang.String
  • java.lang.StringBuilder
  • java.lang.Throwable
  • java.lang.Math(::random())
  • java.lang.Runnable
  • java.lang.Thread

Wiki和源码中有很多详细的开发文档,如果想探索关于YVM的更多内容,请移步浏览.

License

所有代码基于MIT协议

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券