首页
题库
公司真题
专项练习
面试题库
在线编程
面试
面试经验
AI 模拟面试
简历
求职
学习
基础学习课
实战项目课
求职辅导课
专栏&文章
竞赛
搜索
我要招人
发布职位
发布职位、邀约牛人
更多企业解决方案
AI面试、笔试、校招、雇品
HR免费试用AI面试
最新面试提效必备
登录
/
注册
何人听我楚狂声
字节跳动_抖音_后端开发工程师
关注
已关注
取消关注
#声哥今天更新了吗#
求个关注,第一时间在公众号(楚狂声哥)更新
@何人听我楚狂声:
手写 JVM —— 3. 线程私有数据区
前言 本章实现线程私有的运行时数据区,为下一章实现字节码解释器做准备。 本节的代码位于 https://github.com/CN-GuoZiyang/SpicyChickenJVM/tree/954a17c9c7b83e917ddf028c5702d8c3b8c518f4 运行时数据区 在运行 Java 程序时,JVM 需要内存来存放各式各样的数据。JVM 规范将这些内存区域称为运行时数据区。运行时数据区可以分为两类:线程共享的的线程私有的。线程共享的部分在 JVM 启动时就会被创建,在 JVM 关闭时销毁;而线程私有的部分生命周期与线程一致,随着线程创建而创建,随着线程销毁而销毁。 线程共享的区域主要就是堆和方法区。堆用于存放对象实例,由垃圾收集器清理;方法区则存放字段和方法信息、类数据等。逻辑上来说,方法区也属于堆的一部分。 线程私有的区域包括 pc 寄存器和 Java 虚拟机栈。pc 寄存器用于存储当前执行的字节码行号,Java 虚拟机栈用于记录方法调用,由栈帧构成,栈帧中保存了方法运行状态,由局部变量表和操作数栈组成。 本章我们只实现线程私有的数据区,线程公有的部分在下下章实现。本章实现的类都保存在 rtda (runtime-data area)包中。 线程与虚拟机栈 我们首先来实现对一个线程结构的抽象: public class Thread { // 程序计数器 private int pc; // 虚拟机栈 private JvmStack stack; public Thread() { this.stack = new JvmStack(1024); }} 当前只定义了 pc 和 stack 两个属性,分别代表 pc 寄存器和虚拟机栈。创建线程时同时会初始化一个可以容纳 1024 帧的虚拟机栈。 定义 pushFrame 和 popFrame 方法,用于向栈中压入或从栈中弹出帧,实际上只是包装 JvmStack 类的方法。currentFrame 返回当前正在执行的方法的栈帧。 public void pushFrame(Frame frame) { this.stack.push(frame); } public Frame popFrame() { return this.stack.pop(); } public Frame currentFrame() { return this.stack.top(); } 我们使用链表来实现 Java 虚拟机栈,这样从栈中弹出的帧就会自动被自动垃圾回收,同时这样栈可以按需使用空间。 public class JvmStack { private int maxSize; private int size; // 栈顶指针 private Frame _top; public JvmStack(int maxSize) { this.maxSize = maxSize; }} 构造方法中的 maxSize 定义了栈的最大容量。size 保存了当前栈的大小,_top 是栈顶指针,指向栈顶的第一帧。 接着实现 push、pop 和 top 方法,分别向栈中压入、从栈中弹出和返回栈顶的帧,这部分实现很简单,就是链表操作而已: public void push(Frame frame) { if(this.size > this.maxSize) { throw new StackOverflowError(); } if(this._top != null) { frame.lower = this._top; } this._top = frame; this.size ++; } public Frame pop() { if(this._top == null) { throw new RuntimeException("Jvm stack is empty!"); } Frame top = this._top; this._top = top.lower; top.lower = null; this.size --; return top; } public Frame top() { if(this._top == null) { throw new RuntimeException("Jvm stack is empty!"); } return this._top; } 在栈已经满了的情况下,再 push 就会抛出 StackOverflowError 异常。 接着我们就可以着手实现帧结构了,帧是栈链表的基本元素,所以需要一个指针指向它下面的元素(后面的元素)。帧还要保存方法的运行数据,所以还需要保存局部变量表和操作数栈。如下: public class Frame { Frame lower; // 局部变量表 private LocalVars localVars; // 操作数栈 private OperandStack operandStack; public Frame(int maxLocals, int maxStack) { this.localVars = new LocalVars(maxLocals); this.operandStack = new OperandStack(maxStack); }} 执行方法所需要的局部变量表大小核操作数栈深度是由编译器计算好的,可以从 class 文件 method_info 中的 Code 属性中。 局部变量表 局部变量表是一个可以随机访问的结构,按照下标访问,所以我们可以通过数组实现。根据 JVM 规范,每个元素至少可以容纳一个 int 或引用,两个连续的元素可以容纳一个 long 或 double 类型值。 我们定义一个结构 Slot 表示局部变量表的一个槽位,这样局部变量表就可以被实现为一个 Slot 数组。Slot 可以保存一个 int 或者一个索引,我们分开来存储: public class Slot { int num; Object ref;} 局部变量表 LocalVars 实现如下: public class LocalVars { private Slot[] slots; public LocalVars(int maxLocals) { if(maxLocals > 0) { slots = new Slot[maxLocals]; for(int i = 0; i < maxLocals; i ++) { slots[i] = new Slot(); } } }} 接着我们来实现一些数据结构的存储。首先最好实现的是 int 类型,直接将数据保存在槽位的 num 处即可。 public void setInt(int idx, int val) { this.slots[idx].num = val; } public int getInt(int idx) { return slots[idx].num; } float 类型的数据可以先转换成 int 类型,Float 类中有一个方法 floatToRawIntBits 返回表示浮点数的 int 类型位模式,在取出时可以通过 intBitsToFloat 方法再转换为 float。 public void setFloat(int idx, float val) { this.slots[idx].num = Float.floatToRawIntBits(val); } public Float getFloat(int idx) { int num = this.slots[idx].num; return Float.intBitsToFloat(num); } long 类型需要拆成两个 int 变量。第一个槽存储低 32 位,第二个槽存储高 32 位。在取出时再组合到一起即可。 public void setLong(int idx, long val) { this.slots[idx].num = (int) (val & 0x000000ffffffffL); this.slots[idx + 1].num = (int) (val >> 32); } public Long getLong(int idx) { long low = this.slots[idx].num & 0x000000ffffffffL; long high = this.slots[idx + 1].num & 0x000000ffffffffL; return (high << 32) | low; } double 类型可以通过 doubleToRawLongBits 方法转换为 long 类型的位模式,再按照 long 类型存储。取出同理。 public void setDouble(int idx, double val) { setLong(idx, Double.doubleToRawLongBits(val)); } public Double getDouble(int idx) { return Double.longBitsToDouble(getLong(idx)); } 引用值和 int 类似,直接存取即可。 public void setRef(int idx, Object ref) { slots[idx].ref = ref; } public Object getRef(int idx) { return slots[idx].ref; } 根据 JVM 规范,除了以上提到的几种类型,boolean、byte、short 和 char 类型的数据都是直接转换为 int 值处理,不用单独实现。 操作数栈 操作数栈的实现和局部变量表基本一致。使用一个 Slot 数组来保存操作数。size 用于记录栈顶元素的下标。 public class OperandStack { private int size = 0; private Slot[] slots; public OperandStack(int maxStack) { if(maxStack > 0) { slots = new Slot[maxStack]; for(int i = 0; i < maxStack; i ++) { slots[i] = new Slot(); } } }} 后面关于各种类型的入栈和出栈与局部变量表也基本一样,只需要再处理下 size 即可。不做多余解释。 public void pushInt(int val) { slots[size].num = val; size ++; } public int popInt() { size --; return slots[size].num; } public void pushFloat(float val) { slots[size].num = Float.floatToRawIntBits(val); size ++; } public Float popFloat() { size --; int num = this.slots[size].num; return Float.intBitsToFloat(num); } public void pushLong(long val) { slots[size].num = (int) (val & 0x000000ffffffffL); slots[size + 1].num = (int)(val >> 32); size += 2; } public long popLong() { size -= 2; long low = this.slots[size].num & 0x000000ffffffffL; long high = this.slots[size + 1].num & 0x000000ffffffffL; return (high << 32) | low; } public void pushDouble(double val) { pushLong(Double.doubleToRawLongBits(val)); } public Double popDouble() { return Double.longBitsToDouble(popLong()); } public void pushRef(Object ref) { slots[size].ref = ref; size ++; } public Object popRef(){ size --; Object ref = slots[size].ref; slots[size].ref = null; return ref; } 测试 我们修改 Main 类中的 startJVM 方法,在启动时手动创建一个帧,以测试局部变量表和操作数栈。 private static void startJVM(Cmd args) { Frame frame = new Frame(100, 100); test_localVars(frame.localVars()); test_operandStack(frame.operandStack()); } 两个测试方法如下: private static void test_localVars(LocalVars vars) { vars.setInt(0,100); vars.setInt(1,-100); vars.setLong(2,2997924580L); vars.setLong(4,-2997924580L); vars.setFloat(6, 3.1415926f); vars.setDouble(7, 2.71828182845); vars.setRef(9, null); System.out.println(vars.getInt(0)); System.out.println(vars.getInt(1)); System.out.println(vars.getLong(2)); System.out.println(vars.getLong(4)); System.out.println(vars.getFloat(6)); System.out.println(vars.getDouble(7)); System.out.println(vars.getRef(9)); } private static void test_operandStack(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()); } 其实就是将各种类型的数据存储一遍再取出来。启动后输出结果: 100-1002997924580-29979245803.14159252.71828182845nullnull2.718281828453.1415925-29979245802997924580-100100数据与我们存入时一致!
点赞 6
评论 4
声哥今天更新了吗
全部评论
推荐
最新
楼层
暂无评论,快来抢首评~
相关推荐
01-05 13:14
上海得物信息集团有限公司_电商推荐产品经理(准入职员工)
得物内推,得物内推码
服装运营岗位~真实工作体验1.💰待遇 薪资是一天150,包晚饭,有双休,基本不加班,有茶水间小零食无限吃🍪,健身房、员工折扣店,晚上10点后打车免费,过节活动礼包🎁 2.👗工作 服装运营岗,主要技能需要会用Excel、vlookup 工作内容有点像对接商家的客服👩🏻💻并且帮助你的直属上级完成部分工作(不同岗位要求不一样。有的是需要会ps之类的) ⌚上班时间: 早10:00晚7:00or早9:30晚6:30 中午12:00-1:30 有的部门是2点 3.🏠租房 异地实习选择的是在附近租房,我是直接在🍠上找的姐妹续租,也可以在租房app上找,入职后有得物内部租房群 4.面试 投...
上海得物信息集团有限公司公司福利 1262人发布
点赞
评论
收藏
分享
01-05 20:12
蚌埠坦克学院 嵌入式软件开发
卷加班卷不过你们,只能卷摸鱼了
卷加班这件事,本质上是一场没有终点的消耗战。有人比你更晚走,有人比你更能熬,时间一拉长,输赢早就注定。与其在工位上拼时长,不如承认现实:这条路,卷不过。于是只能换个方向,开始卷摸鱼。不是混日子,而是学会在有限的时间内把事情做到位,用更高的效率完成任务,把多余的加班和表演式努力全部剔除。该学习的时候学习,该干活的时候干活,其余时间尽量不被工作吞噬。摸鱼卷到最后,其实拼的是脑子和边界感。既不内耗自己,也不被无休止的加班文化拖下水。
卷__卷不过你们,只能卷...
点赞
评论
收藏
分享
2025-12-09 15:55
湖南大学 安卓
互联网算法薪酬杀疯了
非人才计划都能上80w+,今年互联网大厂为了抢人才真的是杀疯了,壕无人性,太土豪了,小红书更是遥遥领先……算法真的是年年攀升啊,看的都想跳槽了小红书:50k*16+期权,57k*16+签字费京东:42k*20,46k*20字节:42k*15+签字费腾讯:42k*15+签字费+股票小红书今年是真的炸裂,非人才计划都能给到这么多,字节,腾讯,美团他们都有各自的人才计划,基本上都是破百万的岗位,目前看到最高的就是字节给北邮开了228w的年薪,非常夸张,可见大厂对于人才资源倾斜真的很离谱!
码客明:
小红书要冲上市了,不能有差错,狠狠的砸钱
点赞
评论
收藏
分享
01-05 12:43
南方科技大学 后端工程师
腾讯游戏后端一面
一个半小时纯拷打场景和实操(藤子太哈人了)总的来说,这次面试体验非常好,面试官虽然言辞犀利但还算温柔,愿意引导你进行操作,可以学习到很多知识,特别是一些中间件的底层逻辑,让我真正见识了后端的深似海,但我是第一次在面试中遇到需要实操的场景,非常紧张,终端实操和代码编写都写的不好,只完成了一部分,幸运的是,面试官人特别好,会议结束后就给过了,并鼓励我后面继续加油!问项目:1、大文件上传怎么做的2、某个分片上传时间超过了24小时,因此被定时任务清理掉了,应该怎么办?修改清理触发条件/校验文件完整性,断点续传重新触发3、合并文件时java的RandomAccessFile底层是如何实现的4、写文件时,...
查看12道真题和解析
点赞
评论
收藏
分享
评论
点赞成功,聊一聊 >
点赞
收藏
分享
评论
提到的真题
返回内容
全站热榜
更多
1
...
双非非科班2年时间的转码历程
1.8W
2
...
秋招50+场面试手撕算法汇总
1.1W
3
...
双非本秋招寄录
5893
4
...
批判“上岸即胜利”的炫耀
4572
5
...
简历挂麻了?因为你的简历只有“宽度”没有“深度”!
4144
6
...
小鹏汽车二面(没绷住)
4092
7
...
入职第一天:允许自己像个新生
3683
8
...
Caffeine 面经汇总
3633
9
...
26秋招小结 含面经碎碎念版
3584
10
...
2025年牛客年度作者礼盒开箱(礼盒部分)
2719
创作者周榜
更多
正在热议
更多
#
哪些公司在招寒假实习?
#
10264次浏览
123人参与
#
你怎么看待AI面试
#
132970次浏览
741人参与
#
MiniMax求职进展汇总
#
537次浏览
19人参与
#
26年哪些行业会变好/更差
#
15935次浏览
218人参与
#
找工作时的取与舍
#
114935次浏览
847人参与
#
去年的flag与今年的小目标
#
7831次浏览
173人参与
#
卷__卷不过你们,只能卷__了
#
9319次浏览
218人参与
#
写论文的崩溃时刻
#
4756次浏览
124人参与
#
腾讯音乐求职进展汇总
#
147385次浏览
1048人参与
#
关于春招你都做了哪些准备?
#
121964次浏览
702人参与
#
你不能接受的企业文化有哪些
#
9667次浏览
150人参与
#
有深度的简历长什么样?
#
14285次浏览
302人参与
#
晒一晒你收到的礼盒
#
95005次浏览
460人参与
#
求职你最看重什么?
#
150694次浏览
875人参与
#
你都用AI做什么
#
5734次浏览
143人参与
#
入职第一天
#
8626次浏览
186人参与
#
你觉得第一学历对求职有影响吗?
#
219670次浏览
1226人参与
#
机械人求职现状
#
31620次浏览
292人参与
#
现在前端的就业环境真的很差吗
#
490836次浏览
5946人参与
#
工作丧失热情的瞬间
#
346704次浏览
2518人参与
#
一人分享一道面试手撕题
#
19096次浏览
715人参与
牛客网
牛客网在线编程
牛客网题解
牛客企业服务