Golang实现JAVA虚拟机-运行时数据区

news2024/9/27 5:24:21

一、运行时数据区概述

JVM学习: JVM-运行时数据区

运行时数据区可以分为两类:一类是多线程共享的,另一类则是线程私有的。

  • 多线程共享的运行时数据区需要在Java虚拟机启动时创建好,在Java虚拟机退出时销毁。对象实例存储在堆区类信息数据存储在方法区从逻辑上来讲,方法区其实也是堆的一部分。

  • 线程私有的运行时数据区则在创建线程时才创建,线程退出时销毁。pc寄存器(Program Counter):执行java方法表示:正在执行的Java虚拟机指令的地址;执行本地方法:pc寄存器无意义Java虚拟机栈(JVM Stack)。栈帧(Stack Frame),帧中保存方法执行的状态局部变量表(Local Variable):存放方法参数和方法内定义的局部变量。操作数栈(Operand Stack)等。

虚拟机实现者可以使用任何垃圾回收算 法管理堆,甚至完全不进行垃圾收集也是可以的。

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

二、数据类型概述

Java虚拟机可以操作两类数据:基本类型(primitive type)和引用类型(reference type)。

  • 基本类型的变量存放的就是数据本身布尔类型(boolean type)数字类型 (numeric type)整数类型(integral type)浮点数类型(floating-point type)。

  • 引用类型的变量存放的是对象引用,真正的对象数据是在堆里分配的。类型:指向类实例接口类型:用指向实现了该接口的类或数组实例数组类型: 指向数组实例null:表示该引用不指向任何对 象。

对于基本类型,可以直接在Go和Java之间建立映射关系。对于引用类型,自然的选择是使用指针。Go提供了nil,表示空指针,正好可以用来表示null。

三、实现运行时数据区

创建\rtda目录(run-time data area),创建object.go文件, 在其中定义Object结构体,代码如下:

package rtda
type Object struct {
	// todo
}

本节将实现线程私有的运行时数据区,如下图。下面先从线程开始。

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虚拟机栈有大小限制, 且执行线程所需的栈空间超出了这个限制,会导致 StackOverflowError异常抛出。

  • 如果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.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
}

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),
    }
}

目前结构如下图:

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)
}

// 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所示。

第一条指令是ldc,它把3.14f推入栈顶

上面是局部变量表和操作数栈过去的状态,最下面是当前状态。

接着是fstore_1指令,它把栈顶的3.14f弹出,放到#1号局部变量中

fconst_2指令把2.0f推到栈顶

fload_1指令把#1号局部变量推入栈顶

fmul指令执行浮点数乘法。它把栈顶的两个浮点数弹出,相乘,然后把结果推入栈顶

fload_0指令把#0号局部变量推入栈顶

fmul继续乘法计算

fstore_2指令把操作数栈顶的float值弹出,放入#2号局部变量表

最后freturn指令把操作数栈顶的float变量弹出,返回给方法调 用者

五、测试

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())
}

文章转载自:橡皮筋儿

原文链接:https://www.cnblogs.com/Gao-yubo/p/17925207.html

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1334462.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023.12.22 关于 Redis 数据类型 String 常用命令

目录 引言 String 类型基本概念 SET & GET SET 命令 GET 命令 MSET & MGET MSET 命令 MGET 命令 SETNX & SETEX & PSETEX SETNX 命令 SETEX 命令 PSETEX 命令 计数命令 INCR 命令 INCRBY 命令 DECR 命令 DECRBY 命令 INCRBYFLOAT 命令 总结…

【GoLang】Go语言几种标准库介绍(一)

你见过哪些令你膛目结舌的代码技巧&#xff1f; 文章目录 你见过哪些令你膛目结舌的代码技巧&#xff1f;前言几种库bufio&#xff08;带缓冲的 I/O 操作&#xff09;特性示例 bytes (实现字节操作)特性示例 总结专栏集锦写在最后 前言 随着计算机科学的迅猛发展&#xff0c;编…

复试情报准备

英语自我介绍&#xff0c;介绍完老师会根据你的回答用英语问你问题&#xff0c;比如介绍一下你的本科学校&#xff0c;或者家乡什么的。计网过一遍&#xff0c;会问两道题。接下来是重点&#xff0c;我当时是根据我成绩单&#xff0c;问了我本科学过的科目&#xff0c;比如pyth…

【Docker容器精解篇 】深入探索Docker技术的概念与容器思想

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《docker容器精解篇》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言一、Docker 的介绍1.1 Docker 的由来1.1.1 环境不一致1.1.2 隔离性1.1.3 弹性伸缩1.1.4 学习成本 1.2 Doc…

推荐五个免费的网络安全工具

导读&#xff1a; 在一个完美的世界里&#xff0c;信息安全从业人员有无限的安全预算去做排除故障和修复安全漏洞的工作。但是&#xff0c;正如你将要学到的那样&#xff0c;你不需要无限的预算取得到高质量的产品。这里有SearchSecurity.com网站专家Michael Cobb推荐的五个免费…

网站检测有哪些好用的监测工具

目前网站监测工具良莠不齐&#xff0c;网站监控工具有很多&#xff0c;选择合适功能强大的网站监控工具&#xff0c;对我们的业务安全有非常大的帮助。目前市场上好用的一些网站监测工具如德迅云眼、观测云等&#xff0c;它们都提供了网站性能监测、安全防护、故障预警等功能&a…

天呐,我找到财务报表开发的通关密码了!

要问我们IT最不愿做的报表开发有哪些&#xff0c;首当其冲的一定是财务分析。我对开发财务报表这事就一个态度&#xff1a;只要不谈开发财务报表&#xff0c;我们就还是好朋友&#xff0c;谈了会怎样&#xff1f;不好意思&#xff0c;我会破大防。 1、财务的分析逻辑和需求&am…

Azure Machine Learning - 如何使用 GPT-4 Turbo with Vision

介绍如何在Azure中使用GPT-4 Turbo with Vision 关注TechLead&#xff0c;分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff0c;同济本复旦硕&#xff0c;复旦机器人智能实验室成员&#xff0c;阿里云认证的资深架构师&#xff0c;项目管理…

加速计算,为何会成为 AI 时代的计算力“新宠”

随着科技的发展&#xff0c;处理大量数据和进行复杂计算的需求越来越高&#xff0c;人工智能、大数据和物联网等领域更是如此&#xff0c;传统的计算方式已经无法满足这些需求。因此&#xff0c;加速计算作为一种现代计算方式&#xff0c;成了必要的手段。加速计算具有前所未有…

项目应用多级缓存示例

前不久做的一个项目&#xff0c;需要在前端实时展示硬件设备的数据。设备很多&#xff0c;并且每个设备的数据也很多&#xff0c;总之就是数据很多。同时&#xff0c;设备的刷新频率很快&#xff0c;需要每2秒读取一遍数据。 问题来了&#xff0c;我们如何读取数据&#xff0c…

AutoBookmark Adobe Acrobat快速自动批量添加书签/目录

前言 解决问题&#xff1a;Adobe Acrobat快速自动批量添加书签/目录, 彻底告别手动添加书签的烦恼 AutoBookmark 前言1 功能简介2 实现步骤2.1 下载插件2.2 将插件复制到Acrobat文件夹下2.3 自动生成书签 1 功能简介 我们在查看PDF版本的论文或者其他文件的时候, 虽然相比较于…

傻瓜式教学Docker 使用docker compose部署 php nginx mysql

首先你可以准备这个三个服务,也可以在docker compose 文件中 直接拉去指定镜像,这里演示的是镜像服务已经在本地安装好了,提供如下: PHP # 设置基础镜像 FROM php:8.2-fpm# install dependencies RUN apt-get update && apt-get install -y \vim \libzip-dev \libpng…

goland错误:该版本的1%与您运行的windows版本不兼容

创建第一个go语言的hello world后&#xff0c;报错。 需要将 package gotest1 改为 package main main是主程序的入口

【leetcode100-020】【矩阵】旋转图像

【题干】 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 你必须在 原地 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。 【思路】 怎么还整上小学奥数题了&#xff08;不是对角翻转水平/垂…

第11章 GUI Page436 使用缓冲DC, wxBufferedPaintDC

所谓“缓冲DC”&#xff0c;是指将所有图元都先划到一个人眼看不到的“设备上下文”之上&#xff0c;最后再一次性复制到真正的屏幕DC之上&#xff0c;这样我们就看不到中间画的过程了&#xff0c;也就不会感到闪烁了。 注意&#xff0c;这时不能解除ScrolledWindow1的背景擦除…

代码签名的功能实现原理

代码签名是一种用来确保软件或程序来源和完整性的技术&#xff0c;它通过对程序文件进行数字签名和验证来保证软件未被篡改过。下面我将为您介绍代码签名的功能实现原理。 1. 数字证书颁发机构&#xff08;CA&#xff09;&#xff1a; 代码签名的实现依赖于权威的数字证书颁发…

JS 现代化的深克隆

前端手写深拷贝/深克隆是一道回头率超高的笔试题&#xff0c;但笔试版一般不适用于生产环境&#xff0c;JSON 的奇技淫巧和 Lodash 的工具函数也各有缺点。 您知道吗&#xff0c;JS 现在有一种原生方法可以深层复制对象&#xff1f; structuredClone 函数内置在 JS 运行时中&a…

极智芯 | 解读最新全球半导体设计厂商排名 英伟达一骑绝尘 中国韦尔半导体上榜

欢迎关注我,获取我的更多技术分享 大家好,我是极智视界,本文分享一下 最新全球芯片设计厂商排名 英伟达一骑绝尘 中国韦尔半导体上榜。 邀您加入我的知识星球「极智视界」,星球内有超多好玩的项目实战源码和资源下载,链接:https://t.zsxq.com/0aiNxERDq 最近,TrendForc…

C# 主要语言区域

C# 教程 - 主要语言区域 - C# | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/csharp/tour-of-csharp/features 目录 数组、集合和 LINQ 数组 字符串内插 模式匹配 委托和 Lambda 表达式 async/await 属性 数组、集合和 LINQ C# 和 .NET 提供了许多不同的…

I Doc View在线文档预览系统 cmd.json RCE漏洞复现

0x01 产品简介 IDocView是一个在线文档解析应用,旨在提供便捷的文件查看和编辑服务。 0x02 漏洞概述 I Doc View在线文档预览系统 cmd.json 接口处存在命令执行漏洞,攻击者可通过该漏洞在服务器端任意执行代码,写入后门,获取服务器权限,进而控制整个web服务器。 0x03 影…