编辑
2024-07-30
后端
00
请注意,本文编写于 332 天前,最后修改于 332 天前,其中某些信息可能已经过时。

接下来是第四章,这章讲的主要是初步初步构建运行时数据区,在开始写代码之前,我们需要了解JVM的运行时数据区是怎样的,背过八股文的都知道,大概是下面的一样的结构

image.png

主要分为多线程共享和线程独享两个区域,Java虚拟机规范对这块很宽松,对于heap,内存可以连续也可以不连续,可以固定大小也可以动态大小,可以垃圾回收也可以不进行垃圾回收,所以heap部分我们直接套用Go本身带的垃圾回收机制和heap就可以,方法区存储类元数据,先不管,所以这节课主要考虑实现线程独享的虚拟机栈,本地方法栈和程序计数器,知道他们是怎么运作的

JVM操作的类型有两种,分别是基本类型和引用类型,基本类型我们可以和Go对应替换,引用类型我们用指针实现,对于null我们用nil替换,因为后面的章节才真正的实现Object,我们先定义一个空结构体表示他

go
package rtda type Object struct {}

接下来我们就用*Object来表示一个引用,具体的对应关系参照下面的表

image.png

要实现线程独享变量,没有现成怎么行呢?我们定义一个对象表示线程,他应该有

image.png

这三个东西,我们先不管本地方法栈,先实现虚拟机栈和程序计数器

go
type Thread struct { pc int stack *Stack } func NewThread() *Thread { return &Thread{ stack: newStack(1024), // 创建一个新的栈,初始容量为1024 } } func (self *Thread) PC() int { return self.pc // 返回当前线程的程序计数器(PC) } func (self *Thread) SetPC(pc int) { self.pc = pc // 设置当前线程的程序计数器(PC) } 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() // 返回当前线程的栈顶栈帧 }

接下来就是虚拟机栈的实现了,Java虚拟机栈分为一个个栈帧,每一个栈帧有四个部分组成,如图

image.png

先定义Stack,Java虚拟机规范对虚拟机栈没有太多约束,既可以是固定大小也可以自动扩容,如果是固定大小,爆栈后抛出StackOverFlowError异常,如果是动态扩容,当申请不到内存是应该抛出OutOfMemoryError,我们可以直接用链表来实现Stack里面的栈帧,一个栈帧链表,Stack里面包含链表头结点引用和允许的最大深度maxsize,接下来定义Stack

go
type Stack struct { maxSize uint // 栈的最大容量 size uint // 当前栈的大小 _top *Frame // 栈顶的栈帧,栈是作为链表实现的 } func newStack(maxSize uint) *Stack { return &Stack{ maxSize: maxSize, // 初始化栈的最大容量 } } func (self *Stack) push(frame *Frame) { if self.size >= self.maxSize { panic("java.lang.StackOverflowError") // 如果栈已满,抛出StackOverflowError异常 } if self._top != nil { frame.lower = self._top // 将新栈帧的lower指针指向当前栈顶栈帧 } self._top = frame // 将新栈帧设置为栈顶 self.size++ // 栈的大小加1 } func (self *Stack) pop() *Frame { if self._top == nil { panic("jvm stack is empty!") // 如果栈为空,抛出异常 } top := self._top // 获取当前栈顶栈帧 self._top = top.lower // 将栈顶指针指向下一个栈帧 top.lower = nil // 将弹出栈帧的lower指针置空 self.size-- // 栈的大小减1 return top // 返回弹出的栈帧 } func (self *Stack) top() *Frame { if self._top == nil { panic("jvm stack is empty!") // 如果栈为空,抛出异常 } return self._top // 返回当前栈顶栈帧 }

现在还没有真正实现异常,先这样暂停执行

接下来就是定义栈帧,按照之前说的,只要先实现局部变量表和操作数栈

go
type Frame struct { lower *Frame // 栈是作为链表实现的,指向下一个栈帧 localVars LocalVars // 局部变量表 operandStack *OperandStack // 操作数栈 } func NewFrame(maxLocals, maxStack uint) *Frame { return &Frame{ localVars: newLocalVars(maxLocals), // 初始化局部变量表,容量为maxLocals operandStack: newOperandStack(maxStack), // 初始化操作数栈,容量为maxStack } } func (self *Frame) LocalVars() LocalVars { return self.localVars // 返回局部变量表 } func (self *Frame) OperandStack() *OperandStack { return self.operandStack // 返回操作数栈 }

接下来就是局部变量表定义了,局部变量表示按索引访问的,所以我们可以用数组实现,且局部变量表分为一个个Solt,一个Solt是32位,可以存储一个int或者引用,两个Solt可以存储一个long或者double,接下来定义

go
package rtda type Slot struct { num int32 ref *Object }

我们用这个结构来表示一个Solt,接下来就可以定义局部变量表了,再定义一些操作方法

go
import "math" type LocalVars []Slot // 局部变量表,是一个Slot切片的别名 func newLocalVars(maxLocals uint) LocalVars { if maxLocals > 0 { return make([]Slot, maxLocals) // 创建一个长度为maxLocals的Slot切片 } return nil // 如果maxLocals为0,返回nil } func (self LocalVars) SetInt(index uint, val int32) { self[index].num = val // 将int32值存储在局部变量表的指定索引位置 } func (self LocalVars) GetInt(index uint) int32 { return self[index].num // 从局部变量表的指定索引位置获取int32值 } func (self LocalVars) SetFloat(index uint, val float32) { bits := math.Float32bits(val) // 将float32值转换为uint32位表示 self[index].num = int32(bits) // 将uint32位表示存储在局部变量表的指定索引位置 } func (self LocalVars) GetFloat(index uint) float32 { bits := uint32(self[index].num) // 从局部变量表的指定索引位置获取int32值并转换为uint32位表示 return math.Float32frombits(bits) // 将uint32位表示转换为float32值 } // long consumes two slots func (self LocalVars) SetLong(index uint, val int64) { self[index].num = int32(val) // 将int64值的低32位存储在局部变量表的指定索引位置 self[index+1].num = int32(val >> 32) // 将int64值的高32位存储在局部变量表的下一个索引位置 } func (self LocalVars) GetLong(index uint) int64 { low := uint32(self[index].num) // 从局部变量表的指定索引位置获取低32位 high := uint32(self[index+1].num) // 从局部变量表的下一个索引位置获取高32位 return int64(high)<<32 | int64(low) // 将高32位和低32位组合成int64值 } // double consumes two slots func (self LocalVars) SetDouble(index uint, val float64) { bits := math.Float64bits(val) // 将float64值转换为uint64位表示 self.SetLong(index, int64(bits)) // 将uint64位表示存储在局部变量表的指定索引位置和下一个索引位置 } func (self LocalVars) GetDouble(index uint) float64 { bits := uint64(self.GetLong(index)) // 从局部变量表的指定索引位置和下一个索引位置获取int64值并转换为uint64位表示 return math.Float64frombits(bits) // 将uint64位表示转换为float64值 } func (self LocalVars) SetRef(index uint, ref *Object) { self[index].ref = ref // 将引用存储在局部变量表的指定索引位置 } func (self LocalVars) GetRef(index uint) *Object { return self[index].ref // 从局部变量表的指定索引位置获取引用 }

接下来就是操作数栈,操作数栈跟局部变量表一样,也是用Solt表示,栈顶是size,在实现一些栈方法

go
import "math" type OperandStack struct { size uint // 当前操作数栈的大小 slots []Slot // 操作数栈的槽位 } func newOperandStack(maxStack uint) *OperandStack { if maxStack > 0 { return &OperandStack{ slots: make([]Slot, maxStack), // 创建一个长度为maxStack的Slot切片 } } return nil // 如果maxStack为0,返回nil } func (self *OperandStack) PushInt(val int32) { self.slots[self.size].num = val // 将int32值存储在操作数栈的当前位置 self.size++ // 操作数栈的大小加1 } func (self *OperandStack) PopInt() int32 { self.size-- // 操作数栈的大小减1 return self.slots[self.size].num // 从操作数栈的当前位置获取int32值 } func (self *OperandStack) PushFloat(val float32) { bits := math.Float32bits(val) // 将float32值转换为uint32位表示 self.slots[self.size].num = int32(bits) // 将uint32位表示存储在操作数栈的当前位置 self.size++ // 操作数栈的大小加1 } func (self *OperandStack) PopFloat() float32 { self.size-- // 操作数栈的大小减1 bits := uint32(self.slots[self.size].num) // 从操作数栈的当前位置获取int32值并转换为uint32位表示 return math.Float32frombits(bits) // 将uint32位表示转换为float32值 } // long consumes two slots func (self *OperandStack) PushLong(val int64) { self.slots[self.size].num = int32(val) // 将int64值的低32位存储在操作数栈的当前位置 self.slots[self.size+1].num = int32(val >> 32) // 将int64值的高32位存储在操作数栈的下一个位置 self.size += 2 // 操作数栈的大小加2 } func (self *OperandStack) PopLong() int64 { self.size -= 2 // 操作数栈的大小减2 low := uint32(self.slots[self.size].num) // 从操作数栈的当前位置获取低32位 high := uint32(self.slots[self.size+1].num) // 从操作数栈的下一个位置获取高32位 return int64(high)<<32 | int64(low) // 将高32位和低32位组合成int64值 } // double consumes two slots func (self *OperandStack) PushDouble(val float64) { bits := math.Float64bits(val) // 将float64值转换为uint64位表示 self.PushLong(int64(bits)) // 将uint64位表示存储在操作数栈的当前位置和下一个位置 } func (self *OperandStack) PopDouble() float64 { bits := uint64(self.PopLong()) // 从操作数栈的当前位置和下一个位置获取int64值并转换为uint64位表示 return math.Float64frombits(bits) // 将uint64位表示转换为float64值 } func (self *OperandStack) PushRef(ref *Object) { self.slots[self.size].ref = ref // 将引用存储在操作数栈的当前位置 self.size++ // 操作数栈的大小加1 } func (self *OperandStack) PopRef() *Object { self.size-- // 操作数栈的大小减1 ref := self.slots[self.size].ref // 从操作数栈的当前位置获取引用 self.slots[self.size].ref = nil // 将操作数栈的当前位置的引用置空 return ref // 返回引用 }

其他的方法没什么好说的最后PopRef的时候记得把ref置为nil,这样对象才会被Go Runtime给回收。大概的代码和逻辑已经说完了,接下来试试

修改main文件

go
package main import ( "fmt" "jvm/rtda" ) func main() { cmd := parseCmd() // 解析命令行参数 if cmd.versionFlag { fmt.Println("version 0.0.1") // 如果指定了版本标志,打印版本信息 } else if cmd.helpFlag || cmd.class == "" { printUsage() // 如果指定了帮助标志或未指定类名,打印使用说明 } else { startJVM(cmd) // 启动JVM } } func startJVM(cmd *Cmd) { frame := rtda.NewFrame(100, 100) testLocalVars(frame.LocalVars()) testOperandStack(frame.OperandStack()) } func testLocalVars(vars rtda.LocalVars) { vars.SetInt(0, 100) vars.SetInt(1, -100) vars.SetLong(2, 2997924580) vars.SetLong(4, -2997924580) vars.SetFloat(6, 3.1415926) vars.SetDouble(7, 2.71828182845) vars.SetRef(9, nil) println(vars.GetInt(0)) println(vars.GetInt(1)) println(vars.GetLong(2)) println(vars.GetLong(4)) println(vars.GetFloat(6)) println(vars.GetDouble(7)) println(vars.GetRef(9)) } func testOperandStack(ops *rtda.OperandStack) { ops.PushInt(100) ops.PushInt(-100) ops.PushLong(2997924580) ops.PushLong(-2997924580) ops.PushFloat(3.1415926) ops.PushDouble(2.71828182845) ops.PushRef(nil) println(ops.PopRef()) println(ops.PopDouble()) println(ops.PopFloat()) println(ops.PopLong()) println(ops.PopLong()) println(ops.PopInt()) println(ops.PopInt()) }

运行试试

image.png

现在还只是实现了基本结构,后续将继续完善并真正派上用场

本文作者:yowayimono

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!