运行时数据区概述
java虚拟机运行java程序时,需要使用内存来存放各式各样的数据。虚拟机规范把这些内存区域称之为运行时数据区。分为两类,一类是多线程共享的,另一类是线程私有的。多线程共享的运行时数据区需要再java虚拟机启动时创建好,在java虚拟机退出时销毁。线程私有的运行时数据区在创建线程时才创建,线程退出时销毁。
多线程共享的内存区域主要存放两类数据:类数据和类实例(对象)。对象数据存放在堆中,类数据存放在方法区中。堆由垃圾收集器定期清理,程序员不需要考虑对象空间的释放。类数据包括字段和方法信息、方法的字节码、运行时常量池等。
线程私有的运行时数据区用于辅助执行java字节码。每个线程都有自己的pc寄存器和java虚拟机栈。虚拟机栈由栈帧构成,帧中保存方法执行的状态,包括局部变量表和操作数栈等
运行时数据区的结构如下
Go自带gc,所以直接使用Go的堆和垃圾回收器简化工作
数据类型
java虚拟机可以操作两类数据:基本类型和引用类型。基本变量存放的是数据本身,引用类型的变量存放的是对象引用,真正的对象数据是在堆里分配的。
基本类型可以分为布尔类型和数字类型,数字类型又可以分为整数类型和浮点数类型。引用类型分为三种:类类型、接口类型、数组类型。类类型引用指向类实例,数组类型引用指向数组实例,接口实例引用指向实现了该接口的类或数组实例。
Go提供了丰富的数据类型,对于基本类型,可以直接在Go和java之间建立映射关系。对于引用类型,选择指针,Go提供nil用于表示空指针,正好用来表示null。
java虚拟机数据类型
实现运行时数据区
线程
定义Thread结构体
type Thread struct {
pc int
stack *Stack
}
stack字段是Stack(java虚拟机栈)结构体指针 创建Thread实例的方法(栈大小暂时为1024)
func NewThread() *Thread {
return &Thread{stack: newStack(1024)}
}
newStack()方法创建Stack结构体实例,参数表示最多可以容纳多少帧
另外定义如下三个方法
PushFrame()和PopFrame()方法调用Stack结构体相应的方法
func (self *Thread) PushFrame(frame *Frame) {
self.stack.push(frame)
}
func (self *Thread) PopFrame() *Frame {
return self.stack.pop()
}
CurrentFrme()放回当前帧
func (self *Thread) CurrentFrame() *Frame {
return self.stack.top()
}
java虚拟机栈
虚拟机栈结构体定义如下
type Stack struct {
maxSize uint
size uint
_top *Frame
}
maxSize字段保存栈的容量,size字段保存栈的当前大小,_top字段保存栈顶指针
此外,定义一下几个方法
func newStack(maxSize uint) *Stack {
return &Stack{maxSize: maxSize}
}
push()方法把帧压入栈中 如果栈已经满了,按照虚拟机规范应该跑出StackOverflowError异常,这里先暂时panic()暂停程序执行,异常后续实现
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 = frame
self.size++
}
pop()方法将栈顶帧弹出
func (self *Stack) pop() *Frame {
if self._top == nil {
panic("jvm stack is empty!")
}
top := self._top
self._top = top.lower
top.lower = nil
self.size--
return top
}
func (self *Stack) top() *Frame {
if self._top == nil {
panic("jvm stack is empty!")
}
return self._top
}
帧
定义帧结构体,代码如下
type Frame struct {
lower *Frame
localVars LocalVars
operandStack *OperandStack
}
lower字段用来实现栈的链表结构,localVars字段保存局部表量表指针,operandStack字段保存操作数栈指针 newFrame方法创建Frame实例
func newFrame(maxLocals, maxStack uint) *Frame {
return &Frame{localVars: newLocalVars(maxLocals),
operandStack: newOperandStack(maxStack),
}
}
使用链表实现java虚拟机栈
局部变量表
局部变量表是按索引访问的,容易想到使用数组实现。根据java虚拟机规范,这个数组的每个元素至少可以容纳一个int或引用值,连续的两个元素可以容纳一个long或者double
这里使用一个结构体数组来表示局部变量变,使他可以同时表示一个int值或一个引用值
结构体定义
type Slot struct {
num int32
ref *Object
}
num字段存放整数,ref字段存放引用 同样的,定义newLocalVars()方法创建实例,这里的maxLocals表示局部变量表的大小,由编译器预先计算好,代表方法运行时需要保存的最大局部变量数量
func newLocalVars(maxLocals uint) LocalVars {
if maxLocals > 0 {
return make([]Slot, maxLocals)
}
return nil
}
操作局部变量表和操作数栈的指令都是隐含类型的,下面定义一下方法,用来存取不同类型变量,int类型最简单。
func (self LocalVars) SetInt(index uint, val int32) {
self[index].num = val
}
func (self LocalVars) GetInt(index uint) int32 {
return self[index].num
}
float类型可以先转成int类型,再按int变量来处理
func (self LocalVars) SetFloat(index uint, val float32) {
bits := math.Float32bits(val)
self[index].num = int32(bits)
}
func (self LocalVars) GetFloat(index uint) float32 {
bits := uint32(self[index].num)
return math.Float32frombits(bits)
}
long变量需要拆成两个int变量
func (self LocalVars) SetLong(index uint, val int64) {
self[index].num = int32(val)
self[index+1].num = int32(val >> 32)
}
func (self LocalVars) GetLong(index uint) int64 {
low := uint32(self[index].num)
high := uint32(self[index].num)
return int64(high)<<32 | int64(low)
}
double变量先转成long类型,再按照long变量处理
func (self LocalVars) SetDouble(index uint, val float64) {
bits := math.Float64bits(val)
self.SetLong(index, int64(bits))
}
func (self LocalVars) GetDouble(index uint) float64 {
bits := uint64(self.GetLong(index))
return math.Float64frombits(bits)
}
引用类型直接存取即可,对于boolean、byte、shot和char类型可以转出int来处理。
操作数栈
定义操作数栈结构体OperandStack,代码如下
type OperandStack struct {
size uint
slots []Slot
}
操作数栈的大小是编译期已经确定的,所以可以用[]slot实现,size字段用于记录栈顶位置,定义newOperandStack方法创建实例。 和局部变量表类似,定义一些方法操作操作数栈。
先看最简单的int
func (self *OperandStack) PushInt(val int32) {
self.slots[self.size].num = val
self.size++
}
func (self *OperandStack) PopInt() int32 {
self.size--
return self.slots[self.size].num
}
float变量需要转为int,long和double都拆成两个int变量在操作 引用类型:
func (self *OperandStack) PushRef(ref *Object) {
self.slots[self.size].ref = ref
self.size++
}
func (self *OperandStack) PopRef() *Object {
self.size--
ref := self.slots[self.size].ref
self.slots[self.size].ref = nil
return ref
}
弹出引用后,将Slot结构体的ref字段设置为nil,帮助垃圾回收
参考资料:
《自己动手写Java虚拟机》