到目前为止,已经实现了搜索、解析class文件,本章节就来讨论下如何初步实现运行时数据区。
运行时数据区概述
在运行Java程序时,Java虚拟机需要使用内存在存放各式各样的数据。Java虚拟机规范把这些内存区域叫作运行时数据区。运行时数据区可以分成两类:一类是多线程共享的,另一类则是线程私有的。多线程共享的运行时数据去需要在Java虚拟机启动时创建浩,在Java虚拟机退出时销毁。线程私有的运行时数据区则是在线程创建时才创建,线程退出时销毁。
线程私有的运行时数据区用于辅助执行Java字节码。每个线程有自己的pc寄存器和Java虚拟机栈。Java虚拟机栈又由栈帧构成,栈帧中保存方法执行的状态,包括局部变量表和操作数栈等。本章节将开始学习如何初步实现线程私有的运行时数据区。
数据类型
Java虚拟机可以操作两类数据:基本类型和引用类型。基本类型存放的就是数据本身,引用类型的变量存放的是对象引用,真正的对象数据是在堆里分配的。基本类型可以进一步分为布尔类型和数字类型,数字类型又可以进一步分为整数类型和浮点数类型。引用类型可以进一步分为3种:类类型、接口类型和数组类型。类类型引用指向类实例,数组类型引用指向数组实例,接口类型引用指向实现了该接口的类或数组实例。引用类型有一个特殊的值–null,表示该引用不指向任何对象。
之前的章节已经说过了,Go语言提供了非常丰富的数据类型,可以对应Java的基本数据类型。对于引用类型,自然是选择使用指针,Go提供了nil,表示空指针,正好可以用来表示null。而且Go本身也有垃圾回收功能,所以可以直接使用Go的垃圾收集器。
实现运行时数据区
下面将开始实现线程私有的运行时数据区,相关代码位于rtdata包下。
线程
JVM中线程的构成如下:
我们首先就来定义线程结构体,代码位于thread.go文件中:
注:本章代码基本上都会引用到还没有讲到的数据结构,比如Stack结构体还没有定义,*Stack会编译不过,后面讲到的时候会补上,最终会编译通过。
Java虚拟机栈
Java虚拟机规范对Java虚拟机栈的约束相当宽松,Java虚拟机栈可以是连续的空间,也可以时不连续的;可以时固定大小的,也可以是运行时动态扩展的。如果Java虚拟机栈有大小限制,且执行线程所需要的栈空间超出了这个限制,会导致StackOverflowError异常抛出。如果Java虚拟机栈可以动态扩展,但是内存已经耗尽,会导致OutOfMemoryError异常抛出。我们用经典的链表数据结构来实现Java虚拟机栈,这样栈就可以按需使用内存空间,而且弹出的帧也可以及时被GO的垃圾收集器回收。下面定义Stack结构体,相关代码位于jvm_stack.go文件中:
完善thread.go文件,添加初始化线程的方法,newStack()函数创建Stack结构体实例,参数表明Stack最多可以容纳多少栈帧。
栈帧
栈帧结构体位于frame.go文件中:
由Thread、Stack和Frame的结构可以看出,Java虚拟机栈的链表结构如下图所示:
局部变量表
局部变量表时按索引访问的,所以很自然,可以把它想象成一个数组。根据Java虚拟机规范,这个数组的每个元素至少可以容纳一个int或引用之,两个连续的元素可以容纳一个long或double值。这里采用定义一个结构体来存放局部变量表,其结构体位于slot.go文件中:
num字段存放整数,ref字段存放引用,刚好满足需求。其中Object位于object.go文件中,暂时结构体中没有内容,其结构如下:
下面就可以新建局部变量表结构体了,位于local_vars.go文件中:
继续编辑local_vars.go文件,给LocalVars类型定义一些方法,用来存取不同类型的变量。代码这里就不贴出来了,参照项目。顺便说一下,代码中没有给boolean、byte、short和char类型定义存取方法,这些类型的值都可以转换成int来处理。
操作数栈
操作数栈的实现方式和局部变量表雷系,其结构体位于operand_stack.go文件中:
和局部变量表类似,需要定义一些方法从操作数栈中弹出或者推入各种类型的变量,这里依然不贴出代码了。
完善代码
至此,本章最基础单位的代码已经写完,下面来完善下之前没完成的代码。
完善栈帧
1234567// 实例化栈帧func NewFrame(maxLocals, maxStack uint) *Frame {return &Frame{localVars: newLocalVars(maxLocals),operandStack: newOperandStack(maxStack),}}完善虚拟机栈
123456789101112131415161718192021222324252627282930313233343536373839// 返回栈顶指针func (self *Stack) top() *Frame {if self._top == nil {// 若栈时空的,肯定是有bug,需要panicpanic("jvm stack is empty!")}return self._top}// 入栈func (self *Stack) push(frame *Frame) {// 超过栈最大深度报错if self.size > self.maxSize {panic("java.lang.StackOverflowError")}// 将栈顶指针下移if self._top != nil {frame.lower = self._top}self._top = frameself.size++}// 出栈func (self *Stack) pop() *Frame {// 若栈顶指针为空,报错if self._top == nil {panic("jvm stack is empty!")}top := self._top// 弹出栈顶指针self._top = top.lower// 弹出指针的lower指向不再有意义top.lower = nilself.size--return top;}完善线程
1234567891011121314// 入栈,调用虚拟机栈的方法func (self *Thread) PushFrame(frame *Frame) {self.stack.push(frame)}// 出栈,调用虚拟机栈的方法func (self *Thread) PopFrame() *Frame {return self.stack.pop()}// 查看栈顶指针,调用虚拟机栈的方法func (self *Thread) CurrentFrame() *Frame {return self.stack.top()}
本章节就到此结束,下一章来分析下局部变量表和操作数栈实例。