首页
题库
公司真题
专项练习
面试题库
在线编程
面试
面试经验
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
声哥今天更新了吗
全部评论
推荐
最新
楼层
暂无评论,快来抢首评~
相关推荐
昨天 16:50
叠纸游戏_恋与星空-游戏策划(准入职员工)
OPPO内推,OPPO内推码
关于工作环境:base深圳前海 内部配置绝了。一整栋50层都是oppo哒,我的工位可以看到欢乐港湾和大海,零食柜自给,很感动的是每层都提供独立的隔音室,3层饭堂里,23楼是最好吃的 周边很繁华。楼下三层是大商场,午饭后我一般会和其他实习生姐妹去citywalk,好多奶茶店,茶救,奈雪 喜茶,瑞幸,奶白,煲珠公每天换着喝,好幸福 关于工作氛围 感受到相当年轻,相当扁平的氛围,我们组(销售运营部)有三个哥哥姐姐直接对接我,她们人真的很nice,笨笨的我不会时,问她们都会耐心解答~~ 于工作内容 感觉出入还是有的,问了哥哥姐姐,他们收到五六十份简历,面了6个,最后选了我,好感动好幸运。偏大客户运营方...
OPPO公司福利 1232人发布
点赞
评论
收藏
分享
02-22 12:24
北京艺术传媒职业学院 Java
26春招求帮忙看看简历
考研希望渺茫,开始冲春招,大佬帮忙看看简历需要修改的地方谢谢各位大佬祝大佬事事顺心,工作顺利两个项目业务有点重复 第二个是苍穹外卖包装成之前比赛的项目(比赛项目管理端是用django写的)后面打算速成一下测开包装一下写一个测开版本的简历
点赞
评论
收藏
分享
02-12 10:02
门头沟学院 Java
完蛋了
我今年大二,昨天面了一个20几个人的小厂,一上来自我介绍,然后八股,问了redis持久化策略,aop,ioc,transactional啥时候失效,这个说了四五点吧,然后问我nacos知不知道,我说知道一些,回答了注册中心,调用者从那里拉取服务列表,还有给服务发送心跳大概这些,然后问了一个项目问题,然后他就说我这边买什么问题了,然后问我,你才20岁现在大几,可以实习多久,我看你的专业数字媒体技术为什么学这个方向呢,然后就是你有什么想问我的吗,全程15分钟,今天面试结果不太合适😭😭😭😭😭😭太合适,好难过啊家人们,到底为什么😭
sunsunn:
没事 先拿小厂练手,后面面多了就麻木了 加油
查看9道真题和解析
点赞
评论
收藏
分享
02-16 12:16
科华数据股份有限公司_自动化测试工程师(准入职员工)
科华数据内推,科华数据内推码
科华数据 提前批 硬件工程师(2026届)面经投递时间:7月24日,投完简历过后收到测评,5个工作日内完成。7月30日收到笔试通知,笔试内容包括数电模电电力电子方面的内容(我个人遇到模电里反馈组态考得比较多,还有个Buck拓扑电路题)8月6号收到面试通知8月8日HR电话面试,(HR面没啥专业问题)面试过程很轻松:1.自我介绍2.从自我介绍中凝练三个自身优势3.性格自我评价优缺点4.有做过大功率吗5.有面试其他公司吗?手里有offer吗6.有考虑公务员和电网吗7.对科华有了解吗8.有女朋友吗9.问期望薪资待遇,为什么这个期望,组内师兄姐待遇还有一些不太记得了反问:1.公司晋升渠道。答:技术、管理...
点赞
评论
收藏
分享
评论
点赞成功,聊一聊 >
点赞
收藏
分享
评论
提到的真题
返回内容
招聘动态
查看更多
27届简历点评
27届寒假/转正实习汇总
全站热榜
更多
1
...
你们开工红包发了多少?评论抽2人送外卖券
3259
2
...
总结下秋招被问到的关于AI的面试题
2722
3
...
没有不拿offer的义务!
1855
4
...
双非大厂实习
1651
5
...
字节后端Agent一面凉经
1257
6
...
开水加点🍬
969
7
...
没想到我因为一件“小事”彻底破防了
836
8
...
相求问下我的简历该怎么优化?很多hr拿到简历之后就不回复了很难受
821
9
...
实在智能Java二面面经 (仍需沉淀)
804
10
...
大三异地的实习有必要去吗
801
创作者周榜
更多
正在热议
更多
#
开工第一帖
#
12964次浏览
274人参与
#
携程求职进展汇总
#
882620次浏览
5795人参与
#
xx岗简历求拷打
#
4143次浏览
48人参与
#
工作不开心辞职是唯一出路吗
#
8047次浏览
30人参与
#
有转正机会的小厂实习值得去吗?
#
6011次浏览
73人参与
#
掌握什么AI技能,会为你的求职大大加分
#
4398次浏览
201人参与
#
实习期间如何提升留用概率?
#
241474次浏览
1824人参与
#
为什么国企只招应届生
#
238769次浏览
1301人参与
#
参加完秋招的机械人,还参加春招吗?
#
111101次浏览
709人参与
#
哪些公司开春招了?
#
32795次浏览
204人参与
#
秋招你经历过哪些无语的事
#
101355次浏览
597人参与
#
金三银四,你有感觉到吗
#
691695次浏览
6088人参与
#
毕业季等于分手季吗
#
54899次浏览
654人参与
#
牛客租房专区
#
160242次浏览
1921人参与
#
联想求职进展汇总
#
335056次浏览
2220人参与
#
牛友投递互助,不漏校招机会
#
439110次浏览
5243人参与
#
正在春招的你,也参与了去年秋招吗?
#
353083次浏览
2597人参与
#
你最讨厌面试被问什么
#
6249次浏览
81人参与
#
非技术er求职现状
#
139157次浏览
821人参与
#
你觉得今年春招回暖了吗
#
931284次浏览
7233人参与
牛客网
牛客网在线编程
牛客网题解
牛客企业服务