当前位置: 首页 > news >正文

第五章 用Java实现JVM之运行时数据区

用Java实现JVM目录

第零章 用Java实现JVM之随便说点什么
第一章 用Java实现JVM之JVM的准备知识
第二章 用Java实现JVM之命令行工具
第三章 用Java实现JVM之查找Class文件
第四章 用Java实现JVM之解析class文件
第五章 用Java实现JVM之运行时数据区
第六章 用Java实现JVM之指令集和解释器
第七章 用Java实现JVM之类和对象
第八章 用Java实现JVM之方法调用和返回
第九章 用Java实现JVM之数组和字符串
第十章 用Java实现JVM之本地方法调用
第十一章 用Java实现JVM之异常处理
第十二章 用Java实现JVM之结束


文章目录

  • 用Java实现JVM目录
  • 前言
  • 运行时数据区
        • 方法栈和局部变量表
        • 操作栈
        • 方法返回
        • pc计数器
  • 代码实现
        • 局部变量表
        • 操作栈
        • 栈帧
        • 方法栈
        • 线程
  • 测试
  • 总结


前言

    上一篇我们已经实现了`class文件`的解析,今天开启新的征程,继续往下

运行时数据区

    折腾了这么久,现在可以进入核心的地方了——运行时数据区。这玩意,基本算是老生常谈了,面试时候经常聊到。一说到JVM模型,大家不会不加思索的说出来:堆栈。这些词都快听吐了,这里就不在污染大家的耳朵了。现在先把什么JVM规范扔到一旁,我们换个角度来聊这么问题。如果让你来设计JVM内存模型你会这么设计?

    让我来设计啊?那我就直接操作整个内存。什么堆栈,不熟!分配对象直接分配就行了。方法调用这么办?在内存里直接模拟出来一个公共的栈!所有方法统一往里压,执行完了弹出,简单粗暴、效率还高!嗯。效率确实是高,问题也有不少:

  1. 内存这么回收?

  2. 这个公共栈,多线程咋办?方法一多咋办,都挤在一个栈里面,出栈是不是还有等其他线程先出栈了才行

  3. 还有也是关于多线程的问题。公共栈只有一个,线程安全谁来保证,开发吗?那开发指定是不干了。我就是个写代码的,你让我来研究编译原理、由JVM来保证线程安全吗?严格意义上来说,确实是。但是加锁的话,会严重削减性能

    那这不对啊,这么多问题,不能硬上啊,得改改。3个问题中有两个和线程有关,那平时是如何解决线程安全问题的?锁?不合适,性能不好。那就谁也别争,每个线程一份吧。线程各自有一份问题不大,那线程中要有哪些结构?或者换一个问题:线程执行需要哪些东西才行

方法栈和局部变量表

    线程的执行本质上来说就是,方法的调用,而方法的调用必然设计到栈结构。好,方法栈有了。在细想一下,方法执行的过程中有可能传递参数,并且方法体也可以定义变量,这些变量要要存放到哪里?诶,这时候就需要有个数据结构用来存放这些变量了,这就是 局部变量表。那 局部变量表 应该是个什么样的数据结构呢?数组?链表?还是Map?看来看去,好像是Map更为合适一点,key是变量名,value是值。可以是可以,不过嘛,忽略了编译器的作用,要知道jvm解析的是Class文件,而不是Java文件,Class文件中涉及到访问局部变量,会提供索引,这样就没什么好纠结的了。那指定是数组了

     局部变量表 既然是个数组,那数组存放的元素应该是什么?无非就两种,基本类型,和对象。或者说,数字和对象,因为对于计算机来说什么Boolean类型,true啊、false的,真的不熟悉,编译时候统统用0、1来表示了

操作栈

    方法中遇到的变量问题解决了。在考虑下,方法中还有那些情况。有可能还有涉及到一些运算,比如说int a = b + c之类的。这些要这么计算?我们现在可是在做jvm,可没有现成的东西去运算。说来也简单,还是栈。如果要深究的话,要涉及到 逆波兰表达式,不过我们有万能的编译器,他会帮我们优先级弄好,我们只需要根据指令,进行对应的操作就行了。这里《自己动手写java虚机机》书上有个例子。这里就直接引用了:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

方法返回

    现在单个方法需要的东西基本准备妥当,那如果A方法调用了B方法,那么在B方法执行完成之后,A方法怎么回到原来执行的位置,这时候也需要保存执行到哪一行

pc计数器

    方法执行已经执行完成了,接下来在考虑下多线程的场景,多线程情况下,有可能发现线程上下文切换。这时候线程也需要保存执行到哪一行,方便后续调度

    线程私有的内存区域基本已经定义完毕了,接下来就是公共区域——堆。堆这个区域没有明确的规定,可以连续,也可以不连续,给了实现这足够大的自由,主要问题在于内存管理这块。最简单的方式就是依托于Java本身的管理机制,这样我们啥也不用操心,不过嘛,这样子做对我们帮助不大,起不到一个学习的作用。还有就是自己实现一个算法了,既然本着学习的态度来做这个项目的,那就自己实现吧。现在先不涉及复杂的内容,只要有个堆来分配就行了

代码实现

    经过上面的分析,代码其实基本上就已经出来了。接下来进入实战环节

局部变量表

    局部变量表 就是一个数组,但是数组内的元素是主要包含对象和数字,那基本框架其实就已经出来了。先定义个基础类,然后分别实现。Slot代码如下:

@Setter
@Getter
public class Slot<T> {public T getVal() {return null;}public void setVal(T val) {}
}

再来就是两个实现了,一个是数字类型,虽说是数字类型,但是也分为很多:整型、长整型、浮点型。幸运的是,Java已经提供了公共父类,那就是Number类,这就好办了,我们直接利用他来实现。NumberSlot代码如下:

@Setter
@AllArgsConstructor
@NoArgsConstructor
public class NumberSlot extends Slot<Number> {private Number num;@Overridepublic Number getVal() {return num;}@Overridepublic void setVal(Number val) {this.num = val;}
}

说完数字类型,接下来就是对象了,对象那就更多了,不可以一一实现,只能考虑多态来实现。在Java中,我们都知道Object是所有对象的父类。为此,我们也定义个所有对象的起始类——JObject,他现在就是个空对象,什么都没有,就不贴代码了。有了这个起始类之后,引用类型也就出来了。RefSlot代码如下:

@NoArgsConstructor
@AllArgsConstructor
public class RefSlot extends Slot<JObject> {private JObject ref;@Overridepublic JObject getVal() {return this.ref;}@Overridepublic void setVal(JObject val) {this.ref = val;}
}

元素有了,局部变量表 也就出来了,新增LocalVars类,代码如下:

@Getter
public class LocalVars {private Slot[] slots;public LocalVars(int size) {slots = new Slot[size];}public Float getFloatVal(int index) {NumberSlot numberSlot = (NumberSlot) slots[index];return numberSlot.getVal().floatValue();}public void setFloatVal(int index, Float val) {slots[index] = new NumberSlot(val);}public Long getLongVal(int index) {NumberSlot numberSlot = (NumberSlot) slots[index];return numberSlot.getVal().longValue();}public void setLongVal(int index, long val) {slots[index] = new NumberSlot(val);slots[index + 1] = null;}//省略其他代码。。。
}
操作栈

    操作栈也比较简单,就一些出入栈的操作,我们可以借用Java自带的栈来实现,稍微封装下即可。OperandStack代码如下:

/*** 操作栈*/
@Getter
public class OperandStack {private int size;private Stack<Slot> stack;public OperandStack(int size) {if (size > 0) {stack = new Stack();}}public void pushInt(int val) {push(new NumberSlot(val));}public Integer popInt() {return ((Number) pop().getVal()).intValue();}//省略其他代码。。。
}
栈帧

    方法运行需要用到的元素基本差不多了,还剩下一个方法执行行数记录。把这些数据合起来就是 栈帧 了,也就是Jvm方法栈中的基本元素了。StackFrame代码如下:

@Getter
@Setter
@AllArgsConstructor
public class StackFrame {private LocalVars localVars;private OperandStack operandStack;private AtomicInteger returnAddress;public StackFrame(OperandStack stack, Integer maxLocals) {this.operandStack = stack;this.localVars = new LocalVars(maxLocals);this.returnAddress = new AtomicInteger(0);}public StackFrame(Integer maxStackSize, Integer maxLocals) {this.operandStack = new OperandStack(maxStackSize);this.localVars = new LocalVars(maxLocals);this.returnAddress = new AtomicInteger(0);}
}
方法栈

    栈帧 完成之后,就可以写 方法栈 了,方法栈 除了 栈帧,还需要对栈的深度、局部变量表的长度进行控制,毕竟内存不是无限的,不能随意使用。JvmStack代码如下:

package com.hqd.jjvm.rtda;import lombok.Getter;import java.util.Stack;/*** 虚拟机栈*/
@Getter
public class JvmStack extends Stack<StackFrame> {private Integer maxStackSize;private Integer maxLocalSize;private StackFrame top;public JvmStack(Integer maxStackSize,Integer maxLocalSize) {this.maxStackSize = maxStackSize;this.maxLocalSize = maxLocalSize;this.top = new StackFrame(new OperandStack(maxStackSize),maxLocalSize);this.push(top);}public Integer getLocalVarsIntVal(int index) {return this.getTop().getLocalVars().getIntVal(index);}//省略其他代码。。。
}
线程

    剩下还有个程序计数器,归属于线程底下。我们也把他提取出来,作为一个对象。JThread代码如下:

@Getter
@Setter
public class JThread {private AtomicInteger nextPc;private JvmStack jvmStack;public JThread( ) {this.nextPc = new AtomicInteger(0);this.jvmStack = new JvmStack(1024, 1024);}private void recordCurrentStackStatus(StackFrame newFrame) {/*** 记录当前方法返回地址*/getJvmStack().getTop().setReturnAddress(getNextPc());getJvmStack().push(newFrame);setNextPc(new AtomicInteger(-1));}
}

    最后一个内存区域就是堆了,不过现在不需要什么复杂的算法,先创建出来一个byte[]组成的类就行了。JHeap代码如下:

@Data
public class JHeap {private byte[] heap;          // 模拟堆内存的字节数组private int heapSize;         // 堆大小private int top;/*** 简单的内存分配,返回分配起始地址* @param size 需要分配的字节数* @return 分配起始地址,-1表示内存不足*/public int allocate(int size) {if (top + size > heapSize) {return -1; // 堆内存不足}int allocAddress = top;top += size;return allocAddress;}
}

测试

    好了,搞定,现在测试一下今天的成果。最后对CmdCommand#startJVM方法进行改造。代码如下:

private void startJVM(CmdArgs cmdArgs) {ClasspathClassResource cp = new ClasspathClassResource(cmdArgs.getXJreOption(),cmdArgs.getCpOption());ClassParse parse = new ClassParse(cp);try {StackFrame sf = new StackFrame(100,100);testLocalVars(sf.getLocalVars());testOperandStack(sf.getOperandStack());} catch (Exception e) {e.printStackTrace();}}

testLocalVars方法代码如下:

public void testLocalVars(LocalVars vars) {vars.setIntVal(0, 100);vars.setIntVal(1, -100);vars.setLongVal(2, 2997924580L);vars.setLongVal(4, -2997924580L);vars.setFloatVal(6, 3.1415926F);vars.setDoubleVal(7, 2.71828182845);vars.setRefVal(9, null);System.out.println(vars.getIntVal(0));System.out.println(vars.getIntVal(1));System.out.println(vars.getLongVal(2));System.out.println(vars.getLongVal(4));System.out.println(vars.getFloatVal(6));System.out.println(vars.getDoubleVal(7));System.out.println(vars.getRefVal(9));}

testOperandStack方法代码如下:

public void testOperandStack(OperandStack ops) {ops.pushInt(100);ops.pushInt(-100);ops.pushLong(2997924580L);ops.pushLong(-2997924580L);ops.pushFloat(3.1415926F);ops.pushDouble(2.71828182845);ops.pushRef(null);System.out.println(ops.popRef());System.out.println(ops.popDouble());System.out.println(ops.popFloat());System.out.println(ops.popLong());System.out.println(ops.popLong());System.out.println(ops.popInt());System.out.println(ops.popInt());}

新增一个测试类,RuntimeDataTest代码如下:

public class RuntimeDataTest {public static void main(String[] args) {CmdCommand cmdCommand = new CmdCommand();cmdCommand.parseCmd(args);}
}

运行结果如下:

在这里插入图片描述


总结

    今天主要聊得是JVM内存结构,也是重点部分。初步实现了ThreadStackFrameOperandStackLocalVars等线程私有的运行时数据区

http://www.lryc.cn/news/593547.html

相关文章:

  • Linux内核设计与实现 - 第5章 系统调用
  • 堆堆堆,咕咕咕
  • Java行为型模式---中介者模式
  • 【办公类-107-02】20250719视频MP4转gif(削减MB)
  • Triton的核心概念与简单入门
  • 突破研究边界!探索OpenAI o3与o4-mini模型的无限可能
  • Attu-Milvus向量数据库可视化工具
  • 《Linux系统配置实战:NTP时间同步与SSH免密登录全流程指南》​​
  • Linux练习二
  • 低代码平台ToolJet实战总结
  • 网络大提速,RDMA,IB,iWrap
  • windows docker-03-如何一步步学习 docker
  • 游戏开发日志
  • SurfaceView、TextureView、SurfaceTexture 和 GLSurfaceView
  • eNSP综合实验(DNCP、NAT、TELET、HTTP、DNS)
  • 西门子 S7-1500 PLC 电源选型指南:系统电源与负载电源的核心区别
  • 【Linux服务器】-zabbix通过proxy进行分级监控
  • 【初识数据结构】CS61B中的基本图算法:DFS, BFS, Dijkstra, A* 算法及其来历用法
  • JavaSE-接口
  • 枚举类高级用法
  • 嵌入式学习-PyTorch(8)-day24
  • Ubuntu20.04 samba配置
  • 读书笔记:最好使用C++转型操作符
  • UE5制作小地图
  • CSS篇——第二章 六十五项关键技能(下篇)
  • Django3 - Web前端开发基础 HTML、CSS和JavaScript
  • 【C语言进阶】题目练习(3)
  • 【RK3576】【Android14】摄像头MIPI开发调试
  • Android Auto 即将推出新功能
  • 7月19日日记