首页
题库
公司真题
专项练习
面试题库
在线编程
面试
面试经验
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
声哥今天更新了吗
全部评论
推荐
最新
楼层
暂无评论,快来抢首评~
相关推荐
02-21 15:47
上海交通大学 算法工程师
阿里大模型一面
岗位名称:大语言模型算法面试时长:1h–1.5h自评分:8/10是否下一轮:是模型架构与基础原理目前主流大语言模型在架构设计上有哪些异同点?(追问)Decoder-only 和 Encoder-Decoder 在实际应用场景上有什么区别?介绍一下大语言模型中的注意力机制,多头相比单头注意力有何优势?(追问)如果减少头数会发生什么?是否一定性能下降?什么是大语言模型的涌现能力?目前对该现象的研究有哪些发现?(追问)涌现能力是否和模型规模线性相关?什么是 Embedding?词嵌入和句嵌入有何不同?(追问)Embedding 层是否会参与微调?为什么?大语言模型中的 Tokenization 是如...
技术必备题库
点赞
评论
收藏
分享
昨天 14:26
西安电子科技大学 Java
全栈开发者的谎言:什么都会 = 什么都不精?
上周面了一个自称5年全栈的兄弟🤔。简历漂亮得像报菜名:精通 Vue/React,熟悉 Node.js/Go,玩过 K8s,能画原型图,甚至还写过两个 Flutter App。 我只问了一个问题:如果不使用任何框架,Node.js 的 HTTP 模块是如何处理高并发下的内存积压的?他愣了三秒,支支吾吾说:厄...一般我们都用 NestJS,框架处理好了吧?😖那一刻,我看到了无数前端人的缩影:我们拼命想成为无所不能的全栈大神,最后却活成了什么都懂一点、什么都搞不定的API 缝合怪。全栈不等于样样稀松,真正的价值在于深耕核心难题。与其在重复造轮子中消耗精力,不如用RollCode 低代码平台 提...
牛客在线求职答疑中心
点赞
评论
收藏
分享
02-02 19:07
已编辑
河南师范大学 算法工程师
双非春招还有机会进大厂吗?
双非这份简历在春招有竞争力吗?
点赞
评论
收藏
分享
01-13 09:50
哈尔滨工业大学(威海) Java
已经失去大厂梦了
来杭州实习的第三周,天天早九晚六感觉就已经很精神疲惫了,也没有多少私人时间,不敢想象如果是大厂的逆天作息我能活几年。果然人只有真正来上班了才知道自己想要什么,比起天天高强度被压榨拿到一份不错的薪水,我还是更喜欢能边摸鱼边混口饭吃。已经开始准备秋招找国企躺平了,大三想进国企该准备什么,是不是跟私企互联网招聘路子完全不一样,前边的准备是不是一点用没有了
千影逐风:
果然人与人之间的悲伤无法互通,我倒是希望能找到一个朝九晚六的工作
,之前996+246高强度发版直接给我整的ptsd了,现在看大小周都眉清目秀的
点赞
评论
收藏
分享
02-21 16:28
上海交通大学 算法工程师
HTTP 和 HTTPS 区别
面试题简述我们平时开发时经常看到 HTTP 和 HTTPS,那你能说说它们之间的区别吗?为什么我们要用 HTTPS?它到底比 HTTP 多了什么?面试官想听的1、HTTPS 如何在 HTTP 基础上增加安全保障2、是否理解加密、认证、完整性的区别;3、能否举例说明浏览器访问网站时两者的不同面试回答举例HTTP 和 HTTPS 本质上都是应用层协议,用于浏览器与服务器之间传输数据。两者的区别在于:HTTP 是明文传输,而 HTTPS 在传输层加入了 SSL/TLS 加密层,实现了数据的加密、身份认证和完整性校验。举个例子:当我们访问 http://example.com 时,所有请求与响应的数据...
查看6道真题和解析
点赞
评论
收藏
分享
评论
点赞成功,聊一聊 >
点赞
收藏
分享
评论
提到的真题
返回内容
招聘动态
查看更多
27届简历点评
27届寒假/转正实习汇总
全站热榜
更多
1
...
你们开工红包发了多少?评论抽2人送外卖券
3050
2
...
总结下秋招被问到的关于AI的面试题
2714
3
...
没有不拿offer的义务!
1889
4
...
双非大厂实习
1703
5
...
字节后端Agent一面凉经
1241
6
...
开水加点🍬
1016
7
...
实在智能Java二面面经 (仍需沉淀)
799
8
...
没想到我因为一件“小事”彻底破防了
794
9
...
相求问下我的简历该怎么优化?很多hr拿到简历之后就不回复了很难受
774
10
...
双非秋招0offer,去实习转正还是allin春招呢?
769
创作者周榜
更多
正在热议
更多
#
开工第一帖
#
12689次浏览
271人参与
#
携程求职进展汇总
#
882427次浏览
5795人参与
#
xx岗简历求拷打
#
4101次浏览
48人参与
#
工作不开心辞职是唯一出路吗
#
7995次浏览
30人参与
#
有转正机会的小厂实习值得去吗?
#
5954次浏览
73人参与
#
掌握什么AI技能,会为你的求职大大加分
#
4328次浏览
198人参与
#
实习期间如何提升留用概率?
#
241451次浏览
1824人参与
#
为什么国企只招应届生
#
238735次浏览
1301人参与
#
参加完秋招的机械人,还参加春招吗?
#
111090次浏览
709人参与
#
哪些公司开春招了?
#
32725次浏览
204人参与
#
秋招你经历过哪些无语的事
#
101326次浏览
597人参与
#
金三银四,你有感觉到吗
#
691558次浏览
6088人参与
#
毕业季等于分手季吗
#
54880次浏览
654人参与
#
牛客租房专区
#
160191次浏览
1921人参与
#
联想求职进展汇总
#
335013次浏览
2220人参与
#
牛友投递互助,不漏校招机会
#
439075次浏览
5243人参与
#
正在春招的你,也参与了去年秋招吗?
#
353067次浏览
2597人参与
#
你最讨厌面试被问什么
#
6207次浏览
81人参与
#
非技术er求职现状
#
139145次浏览
821人参与
#
你觉得今年春招回暖了吗
#
931216次浏览
7233人参与
牛客网
牛客网在线编程
牛客网题解
牛客企业服务