一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - Golang實現JAVA虛擬機-運行時數據區

Golang實現JAVA虛擬機-運行時數據區

2024-01-08 01:01未知服務器之家 Java教程

Golang實現JAVA虛擬機-運行時數據區 原文鏈接:https://gaoyubo.cn/blogs/8ae1f4ca.html 前置 Golang實現JAVA虛擬機-解析class文件 一、運行時數據區概述 JVM學習: JVM-運行時數據區 運行時數據區可以分為兩類:一類是多線程共享的,另一類則是線

Golang實現JAVA虛擬機-運行時數據區

原文鏈接:https://gaoyubo.cn/blogs/8ae1f4ca.html

前置

Golang實現JAVA虛擬機-解析class文件

一、運行時數據區概述

JVM學習: JVM-運行時數據區

運行時數據區可以分為兩類:一類是多線程共享的,另一類則是線程私有的。

  • 多線程共享的運行時數據區需要在Java虛擬機啟動時創建好,在Java虛擬機退出時銷毀。
    • 對象實例存儲在堆區
    • 類信息數據存儲在方法區
    • 從邏輯上來講,方法區其實也是堆的一部分。
  • 線程私有的運行時數據區則在創建線程時才創建,線程退出時銷毀。
    • pc寄存器(Program Counter):執行java方法表示:正在執行的Java虛擬機指令的地址;執行本地方法:pc寄存器無意義
    • Java虛擬機棧(JVM Stack)。
      • 棧幀(Stack Frame),幀中保存方法執行的狀態
        • 局部變量表(Local Variable):存放方法參數和方法內定義的局部變量。
        • 操作數棧(Operand Stack)等。
Golang實現JAVA虛擬機-運行時數據區

虛擬機實現者可以使用任何垃圾回收算 法管理堆,甚至完全不進行垃圾收集也是可以的。

由于Go本身也有垃圾回收功能,所以可以直接使用Go的垃圾收集器,這大大簡化了工作

二、數據類型概述

Java虛擬機可以操作兩類數據:基本類型(primitive type)和引用類型(reference type)。

  • 基本類型的變量存放的就是數據本身
    • 布爾類型(boolean type)
    • 數字類型 (numeric type)
      • 整數類型(integral type)
      • 浮點數類型(floating-point type)。
  • 引用類型的變量存放的是對象引用,真正的對象數據是在堆里分配的。
    • 類型:指向類實例
    • 接口類型:用指向實現了該接口的類或數組實例
    • 數組類型: 指向數組實例
    • null:表示該引用不指向任何對 象。

對于基本類型,可以直接在Go和Java之間建立映射關系。
對于引用類型,自然的選擇是使用指針。Go提供了nil,表示空指針,正好可以用來表示null。

Golang實現JAVA虛擬機-運行時數據區

三、實現運行時數據區

創建\rtda目錄(run-time data area),創建object.go文件, 在其中定義Object結構體,代碼如下:

package rtda
type Object struct {
	// todo
}

本節將實現線程私有的運行時數據區,如下圖。下面先從線程開始。

Golang實現JAVA虛擬機-運行時數據區

3.1線程

下創建thread.go文件,在其中定義Thread結構體,代碼如下:

package rtda
type Thread struct {
	pc int
	stack *Stack
}
func NewThread() *Thread {...}
func (self *Thread) PC() int { return self.pc } // getter
func (self *Thread) SetPC(pc int) { self.pc = pc } // setter
func (self *Thread) PushFrame(frame *Frame) {...}
func (self *Thread) PopFrame() *Frame {...}
func (self *Thread) CurrentFrame() *Frame {...}

目前只定義了pc和stack兩個字段。

  • pc字段代表(pc寄存器)
  • stack字段是Stack結構體(Java虛擬機棧)指針

和堆一樣,Java虛擬機規范對Java虛擬機棧的約束也相當寬松。
Java虛擬機棧可以是:連續的空間,也可以不連續;可以是固定大小,也可以在運行時動態擴展。

  • 如果Java虛擬機棧有大小限制, 且執行線程所需的棧空間超出了這個限制,會導致 *Error異常拋出。
  • 如果Java虛擬機棧可以動態擴展,但 是內存已經耗盡,會導致OutOfMemoryError異常拋出。

創建Thread實例的代碼如下:

func NewThread() *Thread {
	return &Thread{
		stack: newStack(1024),
	}
}

newStack()函數創建Stack結構體實例,它的參數表示要創建的Stack最多可以容納多少幀

PushFrame()PopFrame()方法只是調用Stack結構體的相應方法而已,代碼如下:

func (self *Thread) PushFrame(frame *Frame) {
    self.stack.push(frame)
}
func (self *Thread) PopFrame() *Frame {
    return self.stack.pop()
}

CurrentFrame()方法返回當前幀,代碼如下:

func (self *Thread) CurrentFrame() *Frame {
	return self.stack.top()
}

3.2虛擬機棧

用經典的鏈表(linked list)數據結構來實現Java虛擬機棧,這樣就可以按需使用內存空間,而且彈出的也可以及時被Go的垃圾收集器回收。

創建jvm_stack.go文件,在其中定義Stack結構體,代碼如下:

package rtda
type Stack struct {
    maxSize uint
    size uint
    _top *Frame
}
func newStack(maxSize uint) *Stack {...}
func (self *Stack) push(frame *Frame) {...}
func (self *Stack) pop() *Frame {...}
func (self *Stack) top() *Frame {...}

maxSize字段保存棧的容量(最多可以容納多少幀),size字段保存棧的當前大小,_top字段保存棧頂指針。newStack()函數的代碼 如下:

func newStack(maxSize uint) *Stack {
    return &Stack{
       maxSize: maxSize,
    }
}

push()方法把幀推入棧頂,目前沒有實現異常處理,采用panic代替,代碼如下:

func (self *Stack) push(frame *Frame) {
	if self.size >= self.maxSize {
		panic("java.lang.*Error")
	}

	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
}

top()方法查看棧頂棧幀,代碼如下:

// 查看棧頂元素
func (self *Stack) top() *Frame {
    if self._top == nil {
       panic("jvm stack is empty!")
    }

    return self._top
}

3.3棧幀

創建frame.go文件,在其中定義Frame結構體,代碼如下:

package rtda
type Frame struct {
    lower *Frame               //指向下一棧幀
	localVars    LocalVars     // 局部變量表
	operandStack *OperandStack //操作數棧
}
func newFrame(maxLocals, maxStack uint) *Frame {...}

Frame結構體暫時也比較簡單,只有三個字段,后續還會繼續完善它。

  • lower字段用來實現鏈表數據結構
  • localVars字段保存局部變量表指針
  • operandStack字段保存操作數棧指針

NewFrame()函數創建Frame實例,代碼如下:

func NewFrame(maxLocals, maxStack uint) *Frame {
    return &Frame{
       localVars:    newLocalVars(maxLocals),
       operandStack: newOperandStack(maxStack),
    }
}

目前結構如下圖:

Golang實現JAVA虛擬機-運行時數據區

3.4局部變量表

局部變量表的容量以變量槽(Variable Slot)為最小單位,Java虛擬機規范并沒有定義一個槽所應該占用內存空間的大小,但是規定了一個槽應該可以存放一個32位以內的數據類型。

在Java程序編譯為Class文件時,就在方法的Code屬性中的max_locals數據項中確定了該方法所需分配的局部變量表的最大容量。(最大Slot數量)

局部變量表是按索引訪問的,所以很自然,可以把它想象成一 個數組。

根據Java虛擬機規范,這個數組的每個元素至少可以容納 一個int或引用值,兩個連續的元素可以容納一個long或double值。 那么使用哪種Go語言數據類型來表示這個數組呢?
最容易想到的是[]int。Go的int類型因平臺而異,在64位系統上是int64,在32 位系統上是int32,總之足夠容納Java的int類型。另外它和內置的uintptr類型寬度一樣,所以也足夠放下一個內存地址。

通過unsafe包可以拿到結構體實例的地址,如下所示:

obj := &Object{}
ptr := uintptr(unsafe.Pointer(obj))
ref := int(ptr)

但Go的垃圾回收機制并不能有效處理uintptr指針。 也就是說,如果一個結構體實例,除了uintptr類型指針保存它的地址之外,其他地方都沒有引用這個實例,它就會被當作垃圾回收。

另外一個方案是用[]interface{}類型,這個方案在實現上沒有問題,只是寫出來的代碼可讀性太差。

第三種方案是定義一個結構體,讓它可以同時容納一個int值和一個引用值。

這里將使用第三種方案。創建slot.go文件,在其中定義Slot結構體, 代碼如下:

package rtda

type Slot struct {
	num int32
	ref *Object
}

num字段存放整數,ref字段存放引用,剛好滿足我們的需求。

用它來實現局部變量表。創建local_vars.go文件,在其中定義LocalVars類型,代碼如下:

package rtda
import "math"
type LocalVars []Slot

定義newLocalVars()函數, 代碼如下:

func newLocalVars(maxLocals uint) LocalVars {
    if maxLocals > 0 {
       return make([]Slot, maxLocals)
    }
    return nil
}

操作局部變量表和操作數棧的指令都是隱含類型信息的。下面給LocalVars類型定義一些方法,用來存取不同類型的變量。
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變量。(用兩個slot存儲)

// long consumes two slots
func (self LocalVars) SetLong(index uint, val int64) {
    //后32位
    self[index].num = int32(val)
    //前32位
    self[index+1].num = int32(val >> 32)
}
func (self LocalVars) GetLong(index uint) int64 {
    low := uint32(self[index].num)
    high := uint32(self[index+1].num)
    //拼在一起
    return int64(high)<<32 | int64(low)
}

double變量可以先轉成long類型,然后按照long變量來處理。

// double consumes two slots
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)
}

最后是引用值,也比較簡單,直接存取即可。

func (self LocalVars) SetRef(index uint, ref *Object) {
    self[index].ref = ref
}
func (self LocalVars) GetRef(index uint) *Object {
    return self[index].ref
}

注意,并沒有真的對boolean、byte、short和char類型定義存取方法,這些類型的值都可以轉換成int值類來處理。

下面我們來實現操作數棧。

3.5操作數棧

操作數棧的實現方式和局部變量表類似。創建operand_stack.go文件,在其中定義OperandStack結構體,代碼如下:

package rtda
import "math"
type OperandStack struct {
    size uint
    slots []Slot
}

操作數棧的大小是編譯器已經確定的,所以可以用[]Slot實現。 size字段用于記錄棧頂位置。
實現newOperandStack()函數,代碼如下:

func newOperandStack(maxStack uint) *OperandStack {
	if maxStack > 0 {
		return &OperandStack{
			slots: make([]Slot, maxStack),
		}
	}
	return nil
}

需要定義一些方法從操作數棧中彈出,或者往其中推入各種類型的變 量。首先實現最簡單的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
}

PushInt()方法往棧頂放一個int變量,然后把size加1。
PopInt() 方法則恰好相反,先把size減1,然后返回變量值。

float變量還是先轉成int類型,然后按int變量處理。

func (self *OperandStack) PushFloat(val float32) {
    bits := math.Float32bits(val)
    self.slots[self.size].num = int32(bits)
    self.size++
}
func (self *OperandStack) PopFloat() float32 {
    self.size--
    bits := uint32(self.slots[self.size].num)
    return math.Float32frombits(bits)
}

把long變量推入棧頂時,要拆成兩個int變量。
彈出時,先彈出 兩個int變量,然后組裝成一個long變量。

// long 占兩個solt
func (self *OperandStack) PushLong(val int64) {
    self.slots[self.size].num = int32(val)
    self.slots[self.size+1].num = int32(val >> 32)
    self.size += 2
}
func (self *OperandStack) PopLong() int64 {
    self.size -= 2
    low := uint32(self.slots[self.size].num)
    high := uint32(self.slots[self.size+1].num)
    return int64(high)<<32 | int64(low)
}

double變量先轉成long類型,然后按long變量處理。

// double consumes two slots
func (self *OperandStack) PushDouble(val float64) {
    bits := math.Float64bits(val)
    self.PushLong(int64(bits))
}
func (self *OperandStack) PopDouble() float64 {
    bits := uint64(self.PopLong())
    return math.Float64frombits(bits)
}

彈出引用后,把Slot結構體的ref字段設置成nil,這樣做是為了幫助Go的垃圾收集器回收Object結構體實例。

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
}

四、局部變量表和操作數棧實例分析

以圓形的周長公式為例進行分析,下面是Java方法的代碼。

public static float circumference(float r) {
    float pi = 3.14f;
    float area = 2 * pi * r;
    return area;
}

上面的方法會被javac編譯器編譯成如下字節碼:

00 ldc #4
02 fstore_1
03 fconst_2
04 fload_1
05 fmul
06 fload_0
07 fmul
08 fstore_2
09 fload_2
10 return

下面分析這段字節碼的執行。

circumference()方法的局部變量表大小是3,操作數棧深度是2。
假設調用方法時,傳遞給它的參數 是1.6f,方法開始執行前,幀的狀態如圖4-3所示。

Golang實現JAVA虛擬機-運行時數據區

第一條指令是ldc,它把3.14f推入棧頂

Golang實現JAVA虛擬機-運行時數據區

上面是局部變量表和操作數棧過去的狀態,最下面是當前狀態。

接著是fstore_1指令,它把棧頂的3.14f彈出,放到#1號局部變量中

Golang實現JAVA虛擬機-運行時數據區

fconst_2指令把2.0f推到棧頂

Golang實現JAVA虛擬機-運行時數據區

fload_1指令把#1號局部變量推入棧頂

Golang實現JAVA虛擬機-運行時數據區

fmul指令執行浮點數乘法。它把棧頂的兩個浮點數彈出,相乘,然后把結果推入棧頂

Golang實現JAVA虛擬機-運行時數據區

fload_0指令把#0號局部變量推入棧頂

Golang實現JAVA虛擬機-運行時數據區

fmul繼續乘法計算

Golang實現JAVA虛擬機-運行時數據區

fstore_2指令把操作數棧頂的float值彈出,放入#2號局部變量表

Golang實現JAVA虛擬機-運行時數據區

最后freturn指令把操作數棧頂的float變量彈出,返回給方法調 用者

Golang實現JAVA虛擬機-運行時數據區

五、測試

main()方法中修改startJVM:

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())
}
Golang實現JAVA虛擬機-運行時數據區

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 国产精品亚洲精品青青青 | 亚洲一区二区三区免费视频 | 91国内在线国内在线播放 | 欧美涩区 | 99热在线这里只有精品 | 青草国产在线观看 | 人妖欧美一区二区三区四区 | 美女脱了内裤让男桶爽 | 国产成人免费片在线观看 | 国产一区在线 | 4455四色永久免费 | 免费在线观看日韩 | 啊啊啊好大视频 | 国产91精选学生在线观看 | 天美传媒果冻传媒星空传媒 | 四虎影视4hutv最新地址在线 | 手机免费在线视频 | 超强台风免费观看完整版视频 | 黑人巨大vs北条麻妃在线 | 91混血大战上海双胞胎 | 亚洲天堂在线视频播放 | 视频一本大道香蕉久在线播放 | 久久伊人中文字幕有码 | 日本高清视频在线的 | 91短视频在线免费观看 | 色综合九九| 俄罗斯三级在线观看级 | 99年水嫩漂亮粉嫩在线播放 | 夫妻性生活在线 | 国产在线精品一区二区高清不卡 | 国产成人亚洲精品一区二区在线看 | 女人用粗大自熨喷水在线视频 | 久久成人国产精品一区二区 | 操女人的b | 饱满奶大30p | 国产卡一卡二卡三乱码手机 | 亚洲va在线va天堂成人 | 秘书小说| 日韩一区在线播放 | 小柔的性放荡羞辱日记动漫 | 国产精品资源在线观看 |