首页
题库
公司真题
专项练习
面试题库
在线编程
面试
面试经验
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-04 16:55
海康威视_技术支持部_云存储开发工程师(准入职员工)
沐瞳科技内推,沐瞳科技内推码
游戏运营问题:看你以往经历没有涉及游戏,为什么想进入这行?深挖活动运营经历:评估效果会关注哪些指标?新游戏上线后,你会优先关注哪些数据?如果游戏下载量、留存、回流同时出问题,先解决哪个?平时玩什么游戏?为什么喜欢它?(建议提前准备游戏分析)你觉得《MLBB》有哪些可以改进的地方?📚 高频考点整理▪️ 游戏运营理解:内容搭建、版本迭代、用户/渠道运营、数据分析缺一不可。▪️ 数据指标:留存率、ARPU、付费率、活跃玩家分析是核心。▪️ 新手引导设计:简洁提示+适当奖励+难度梯度是关键。▪️ 活动策划:节日活动、社区互动、召回机制都能提升在线人数。🔍 行业工具推荐数据分析常用:七麦、蝉大师、S...
点赞
评论
收藏
分享
01-04 16:54
快手_机器学习算法部_机器学习算法工程师(准入职员工)
霸王茶姬内推,霸王茶姬内推码
市场部/管培生/运营岗📖 1. HR初面(电话/视频):基础筛选,约20分钟 2. 部门主管复试(现场/视频):专业能力考核,约30-40分钟 3. 总经办终面(现场):综合评估与岗位匹配度,约20分钟 TL参考:投递→1天内HR面→3天内复试→终面→2-3天出结果 - “简单自我介绍一下” ✅ 重点:突出与岗位相关的经历(如活动策划、数据分析),结合品牌调性(国风、年轻化)举例。 - “你了解霸王茶姬吗?最近的市场热点是什么?” ✅ 必答知识点: - 品牌定位:“原味鲜奶茶”赛道,对标星巴克的“东方茶饮文化” - 爆款单品:伯牙绝弦(年销1亿杯+) - 近期热点:春节翻译争议(Lunar ...
点赞
评论
收藏
分享
2025-11-21 01:02
字节跳动_测试开发(实习员工)
Every ending is a new beginning
给自己忙碌的三年划上一个句号实习结束开始当社会人啦
chence_cc:
有意向转测开的可以跟
@后端转测开第一人
聊聊
点赞
评论
收藏
分享
01-03 13:55
广东工业大学 Java
想做个计算机应届就业调查,毕业生多少比例能找到对口工作(全班)
点赞
评论
收藏
分享
评论
点赞成功,聊一聊 >
点赞
收藏
分享
评论
提到的真题
返回内容
全站热榜
更多
1
...
🔥2026创作新起点:《新年启航计划》来袭,三大赛道等你来冲!
1.2W
2
...
一大波手撕正在靠近!
1.0W
3
...
为什么你的实习是“无效实习”?又该如何做
4365
4
...
uu们,面试的时候一定要大大方方的啊!
2425
5
...
腾讯游戏后端一面
2328
6
...
双非终于上岸了!!!!
2325
7
...
快手Java日常实习一面
2186
8
...
千里智驾毁约
2110
9
...
帕斯亚科技UE客户端面经
1984
10
...
双非本科,嵌入式秋招上岸的一次总结
1755
创作者周榜
更多
正在热议
更多
#
有深度的简历长什么样?
#
2017次浏览
39人参与
#
应届生第一份工作最好去大厂吗?
#
123563次浏览
1093人参与
#
入职第一天
#
1813次浏览
27人参与
#
非技术er求职现状
#
127007次浏览
772人参与
#
你不能接受的企业文化有哪些
#
1469次浏览
34人参与
#
工作后会跟朋友渐行渐远吗
#
54792次浏览
401人参与
#
实习最想跑路的瞬间
#
107196次浏览
649人参与
#
CVTE工作体验
#
17282次浏览
39人参与
#
双非本科的出路是什么?
#
192389次浏览
1516人参与
#
帆软软件工作体验
#
8557次浏览
34人参与
#
我的上岸简历长这样
#
757115次浏览
11284人参与
#
秋招感动瞬间
#
111712次浏览
509人参与
#
机械人选offer,最看重什么?
#
152287次浏览
789人参与
#
运营/市场/管培生岗位评价
#
28908次浏览
179人参与
#
多益网络求职进展汇总
#
60220次浏览
272人参与
#
为什么那么多公司毁约
#
216969次浏览
1463人参与
#
上班苦还是上学苦呢?
#
317686次浏览
2047人参与
#
2023届毁约公司名单
#
234194次浏览
1063人参与
#
小米求职进展汇总
#
997524次浏览
6500人参与
#
秋招想进国企该如何准备
#
119732次浏览
602人参与
牛客网
牛客网在线编程
牛客网题解
牛客企业服务