基于Golang的Java虚拟机的设计与实现

2019-11-11 13:14张世杰
电脑知识与技术 2019年27期

张世杰

摘要:该文介绍了基于Golang的GOJVM的总体设计以及各功能模块的实现,通过功能测试说明它支持自动拆装箱、反射等Java语言特性,并对Java1.5-1.8的程序可以正确解析并解释执行。

关键词:Java;Golang;Java虚拟机

中图分类号:TP311    文献标识码:A

文章编号:1009-3044(2019)27-0077-02

1 绪论

在企业开发中,Java虚拟机的底层运行及结构设计非常重要。然而学习Java的运行原理、特性实现以及相关虚拟机以及操作系统的底层知识对企业运维人员却存在一定的困难[1]。因此,基于Golang设计并实现一款Java虚拟机GOJVM(Golang Java virtual machines)[2],摒弃繁杂的细节,通过GOJVM的运行和源码,从底层开始,认识虚拟机理论、操作系统以及相关数据结构和算法将有助于学习者尽快掌握相关知识。

2 总体设计

GOJVM虚拟机根据功能需要分为命令行参数和JVM虚拟机两大模块,具体的各个功能模块如图1所示。

GOJVM调用CMD进行命令行参数解析,判断是否启动JVM。正常启动后根据命令行参数或是系统变量,首先调用classpath模块,解析并获取JRE路径;然后调用classfile模块加载标准库类文件,解析和初始化虚拟机;通过调用rtda模块申请并维护为Java提供寄存器、计数器、方法栈等运行时的数据区;最后调用解释器模块,启动指定类文件的main函数。

2.1命令行参数模块

为了模拟Oracle的Java指令,GOJVM需要一个命令行工具用于接受选项、主类名、main()方法三组参数,进而确定GOJVM的运行方式,同时设计了一个-j指令,用于打印所使用的JRE路径和JRE路径的来源[3]。另外,设计-showClass和-log两个选项用于使用户清晰地看到Java运行的方式和类加载过程。-showClass指定虚拟机运行时将类加载过程打印到控制台,-log指定虚拟机打印出程序执行过程中执行的Java字节码指令。Golang标准库中自带了flag包,通过调用flag.BoolVar、flag.StringVar、flag.Parse等方法将命令行参数解析并注入一个CMD结构体之中,最终返回CMD结构体指针。

2.2 JVM模块

本模块首先接受CMD模块封装好的命令行参数、运行类名以及Java程序参数;然后调用classpath模塊解析JRE路径;再通过调用运行时数据区模块,初始化类加载器,对JRE路径下的标准库进行加载;最后初始化运行时数据区,返回一个包装命令行参数、Class Loader、主线程的JVM对象[4]。

1)classpath模块

GOJVM使用class path进行类的搜索,同时使用Oracle JDK附带的JRE来作为Java类库。在实现中,类路径分为三类,分别为启动类路径(bootstrap classpath)、扩展类路径(extension classpath)和用户类路径(user classpath)[5]。具体流程如图2所示。

由于GOJVM需要能从多种形式的类路径中查找class文件,因此将类路径抽象为一个接口,Golang定义如下:

需要支持的类路径形式有目录形式、zip或jar包形式、组合类路径模式。

2)classfile模块

classpath模块已经将class文件正确加载到内存中,但是二进制字节码[7] 的文件内容直接使用并不方便,因此需要对文件内容进行解析和封装。抽象出的classfile模块严格按照Java虚拟机的规范对class文件进行解析。Java类文件格式通过使用常量池,在一定程度上实现了其紧凑性[6]。

3)运行时数据区模块

虚拟机在运行程序时,需要存储空间存放运行时所需要的数据,这个区域就是运行时数据区。Java虚拟机规范定义了在程序执行过程中使用的各种运行时数据区域[7]。

4)指令模块

虚拟机定义了205条指令,并依据虚拟机规范逐个实现这些指令。Java字节码指令自带了操作数类型,分为常量指令、存储指令、加载指令、数学指令、操作数栈指令、比较指令、转换指令、引用指令、控制指令、扩展指令和保留指令。一个指令的执行首先获取该指令的操作数,其次执行指令相应的操作。

5)解释器模块

解释器主要是根据Java程序的指令运行,直至程序结束。核心是通过loop()函数循环执行计算pc、解码指令、执行指令三个步骤。

6)本地方法模块

对操作系统中的基本系统调用主要通过本地方法完成[10]。首先完成本地方法注册表,将已经实现的所有本地方法,定义一个go文件并注册在一个map之中,使用类名、方法名、方法描述符来确定一个方法。

然后设计本地方法调用功能,在本地方法调用时,使用虚拟机栈frame的0XFE和0XFF两条指令,构造一个空的虚拟机栈帧,从本地方法注册表查找本地方法实现并执行[8]。

3 系统测试

1)命令行参数模块测试

[功能 功能说明 测试用例 测试结果 打印帮助 打印出GOJVM命令行帮助信息 -help、-? 通过 指定classpath 指定classpath搜寻和加载类文件 -cp、-classpath 通过 打印classpath路径 打印出classpath的来源类型和值 -j 通过 打印类加载过程 类加载打印出类文件路径以及类名 -showClass 通过 打印指令 打印出解释器执行的字节码指令 -log 通过 打印版本 输出GOJVM版本信息 -version 通过 ]

2)classpath模块测试

[功能 功能说明 测试用例 测试结果 解析出classpath 解析出启动类路径、扩展类路径

用户类路径三种类路径 JAVA_HOME 通过 读取class文件 将class文件的内容加载进内存 HelloWorld 通过 ]

3)classfile模块测试

[功能 功能说明 测试用例 测试结果 检测class版本 对class文件校验

不支持版本程序抛出异常 Jdk12编译的HelloWorld 通过 解析class文件 从文件内容中解析出类的各项数据 Stack 通过 ]

4)集成测试

软件测试是为了发现错误而执行程序的过程[14]。本地方法模块、运行时数据区模块、解释器模块等与其他模块耦合较深,因此不参与单元测试,直接进行集成功能测试。

[功能 功能说明 测试用例 测试结果 HelloWorld 打印hello world,測试程序能否运行 HelloWorld.class 通过 数学支持 测试数学相关指令是否正确 MathTest.class 通过 字符串支持 测试字符串拼接、输出功能 StringTest.class 通过 函数调用与返回 测试能否正常获取函数返回值 FibonacciTest.class 通过 异常处理 测试Java异常处理机制支持 ExceptionTest.class 通过 反射支持 测试能否实现Java反射功能 ArrayClassTest.class 通过 Switch支持 测试switch功能 SwitchTest.class 通过 ]

4 结论

本文在对Java虚拟机理论和设计规范的详细研究基础之上,完成了基于Golang的Java虚拟机GOJVM的实现,并对Java相关功能在该虚拟机上进行了测试。目前该虚拟机实现仅支持到Java 8,今后将会继续改进对Java 9、10、11、12的支持。

参考文献:

[1] 王胜利.浅谈Java虚拟机的内部机制[J].军民两用技术与产品,2017(16):57.

[2] Meyerson J.The Go Programming Language[J]. IEEE Software, 2014,31(5):104.

[3] 张秀宏.自己动手写Java虚拟机[M].北京:机械工业出版社,2016.

[4] 周志明.深入理解Java虚拟机[M]. 北京: 机械工业出版社, 2013.

[5] 张洪娜,刘怡俊, 艾君锐. Java Class文件的结构分析及其解析执行[J].计算机应用与软件, 2011,28(7):180-182.

[6] Tim Lindholm, Frank Yellin. Java Virtual Machine Specification[Z]// The Java Virtual Machine Specification: Java SE. 2016.

[7] Tim Lindholm, Frank Yellin. Java Virtual Machine Specification[Z]// The Java Virtual Machine Specification: Java SE. 2016.

[8] 安百俊,高栋,张伟,等.通过Java调用本地方法[J].微处理机, 2011,32(2):41-44.

【通联编辑:梁书】