TotoroVM 原型新鲜出炉

    经过两个星期的努力,我自己DIY的JVM原型基础功能完工,暂定名为TotoroVM。原型是用Java实现的,待测试稳定后便推进到第二阶段,移值到c++平台。

 

包含功能:

1. class文件读取解析

    《JVM规范》中定义了class文件的格式,基本上是以c类似语言的struct的语法定义的,所以有了借助javacc自动解析class文件的想法。主要步骤为:

1. 定义struct语法规则format.jj。

规则中描述了两个实体StructNode和FieldNode:

StructNode代表了一个struct结构,如: ClassFile, CpInfo...

FieldNode代表了struct结构中的元素,如:magic, majorVersion, minorVersion, constantPool...

FieldNode的类型可以是基础类型,如:U1, U2, U4, F4, F8,或者是StructNode类型,如:CpInfo constantPool[i]。

StructNode中包含多个FieldNode

一些特殊规则也有用到,需要额外支持

switch ... case,如:switch CpInfo.tag case 1 CONSTANTUtf8Info...

定义数组长度,如:constantPool[constantPoolLength]...

数组元素所占额外空间,如:CONSTANTDoubleInfo 占两格constantPool空间...

数组起始下标,如:constantPool[]下标从1开始...

2. 准备符合struct语法规则的数据文件format.txt

如果前一步骤准备充分,那这一步只需要从《JVM规范中》把struct结构复制过来,稍作修改即可。

3. 运用javacc将format.txt映射成StructNode和FieldNode的语法树

执行javacc命令,生成解释器FormatParser

执行FormatParser,输出若干个StructNode,每个StructNode下有多个FieldNode

4. 为StructNode生成Java映射类

一个StructNode对应一个Java类,类名是StructNode的name,每个StructNode下的FieldNode生成一个字段,同时为每个字段生成一个@Format注解,用来保存FieldNode除了名称和类型之外的信息,如:数组长度,switch-case语义数据,数组特殊规则...

5. 为某些映射类添加实用方法

这一步是为了以后使用这些对象方便而设的,如:ClassFile对象中有常量表ConstantPool,是以数组结构保存,如果要按名称搜索就需要另外的方法。添加实用方法有多种实现方式,如:继承父类,派生子类,代理模式封装,进一步扩展format.jj的语法等。我选择了代理类封装的方式来实现。

6. 解析class文件生成ClassFile语法树

至此,所以class文件的格式信息已经包含在映射类和它的封装对象上,终于可以看到些实质成果了,写一个ClassParser类,从根节点ClassFile开始,遍历所有包含@Format的字段,依次从class文件中抽取数据,遇到数组和Object类型的字段,就递归调用ClassParser。最终就可以输出一棵以ClassFile为根节点的语法树。

* 附件有我定义的format.jj和format.txt供参考

2. bytecode指令执行系统 

    先做这一部分是因为想第一时间看到结果,众所周知,Java的对象和数组都是保存在堆(Heap)中,而局部变量(包含基本类型和对象引用)保存在栈中。所以在没有实现Heap和常量表(ConstantPool)的情况下,方法中只要不是和对象,数组,方法调用有关的指令,还是可以执行的。

    class文件中的代码段在ClassFile => MethodInfo => CodeAttribute结构中,主要包含了maxLocals,maxStacks和code[]属性。要了解这几个属性就不能不提到JVM的执行系统结构。

    JVM使用的是基于栈(Stack)的结构,即指令的操作数和操作结果都是保存在栈中,而不是一般物理机的寄存器。如:两个整数相加的指令IADD,就是将栈顶的头两个元素(必须是int)出栈,把它们相加之后的结果再入栈。maxStacks属性就是java在编译过程中,编译器所计算出的这段代码执行过程中最多需要多少栈空间,而maxLocals,由于程序的局部变量也是保存在栈中,显然在一段代码中用于提供操作数的栈和局部变量栈是不能重叠和交叉的,所以每段代码执行的栈分配为栈的最前面maxLocals个空间保留给局部变量使用,中间一段固定长度由JVM实现自行决定和支配,一般是用来保存方法调用的返回地址,最后maxStacks个空间就留给了操作数栈。栈的空间固定为四个字节,在所有的基本类型加上对象引用中,除了long和double需要占两个空间外,其它一律占一个空间,不足4字节的,多余字节留空。

    下面可以开始解释执行bytecode的指令了,JVM每个指定占一个字节,即0-255,JVM支持了200多个指令,所以接下来做的事情就是写一个大的switch-case,把这200多个指令一一解释一遍,和对象,数组,方法调用有关的 new系列, get/put系列,array系列,invoke系列,return系列还有case, instanceof暂时跳过。没有什么难度,纯体力活,不再赘述。

3. Constant Pool常量池生成

待续...

4. Heap堆内存管理

待续...

5. 本地方法调用

待续...

 

你可能感兴趣的:(java,jvm,TotoroVM)