新闻详情

新闻详情

首页 / 资讯中心 / 详情

【从0到1学习JVM · 12】三行加法代码,JVM解释器在底层究竟是怎么跑完的?

发布时间:2026/10/1 20:19:11来源:尧图网络
【从0到1学习JVM · 12】三行加法代码,JVM解释器在底层究竟是怎么跑完的?
前言很多人觉得看字节码像看天书其实只要拿最简单的三行加法代码走一遍JVM 解释器的运行机制就全明白了。本文用真实指令流和状态变化带你一步步看透数据在栈帧里是怎么跳动的。文章目录前言一、为什么算个加法需要8条指令二、执行前先看清栈深与槽位两张底牌三、数据是怎样在栈和表之间跳跃的3.1 赋值阶段为什么存个变量非要先入栈3.2 计算阶段加法指令是怎样把栈顶清空的3.3 归仓阶段计算结果如何落进目标槽位四、执行到return时栈帧是怎样瞬间消失的五、写在最后一、为什么算个加法需要8条指令平时写业务代码三行加法随手就敲出来了packagecom.crayontech.jvm;publicclassBytecodeExecutionDemo{publicstaticvoidmain(String[]args){inta10;intb20;intcab;}}在高级语言层面这不过是两次赋值加上一次求和。如果放到 x86 或 ARM 这种真实的物理 CPU 上跑编译器往往只需要两条汇编指令把数字加载到寄存器然后执行一次add计算就完成了。但如果你用javap -c -v反编译这段代码会发现 JVM 生成的指令足足有 9 条0: bipush 10 2: istore_1 3: bipush 20 5: istore_2 6: iload_1 7: iload_2 8: iadd 9: istore_3 10: return除去最后的return光是完成这两次赋值和一次加法解释器就老老实实执行了 8 条指令。为什么搞得这么繁琐因为 JVM 是一台纯软件实现的虚拟计算机它的指令集是基于栈的架构。在物理硬件上CPU 可以直接命令“把 1 号寄存器和 2 号寄存器的值相加结果写回 1 号寄存器”。但 JVM 不能这么干。不同 CPU 架构的寄存器设计千差万别如果字节码里写死了具体寄存器的编号Java 的跨平台承诺当场就会作废。为了彻底摆脱硬件平台的绑架JVM 把所有运算都设计成了“零地址指令”。像iadd这种指令本身不带任何参数它在执行时永远只认一个地方操作数栈的栈顶。这就决定了解释器的工作模式不管你想算什么数据都必须先从外界搬到操作数栈由解释器在栈顶完成计算再把结果从栈顶搬回局部变量表。当前方法栈帧JVM 执行引擎解释器指示下一条指令偏移读取变量入栈 iload / bipush提供数据源执行运算 iadd运算结果回写 istore程序计数器记录当前字节码行号指令译码器取指与解析操作码局部变量表数据仓库 / Slot 槽位操作数栈计算工作台 / LIFO 栈看似多跑了几步但它换来了极致的通用性。只要宿主机上有兼容的 JVM这段字节码拿到任何芯片上都能跑出完全一致的结果。二、执行前先看清栈深与槽位两张底牌在解释器真正开始执行第 0 行指令之前这间“计算工作室”的格局就已经彻底定死了。很多人以为栈帧里的局部变量表和操作数栈会随着代码运行动态扩容其实并不是。在javac把 Java 源码编译成.class文件的那一刻这个方法需要占用多大的内存空间就已经被静态计算得清清楚楚。来看javap输出里的这行核心元数据Code: stack2, locals4, args_size1这行元数据给出了三个关键数字args_size1传入方法的参数个数。虽然我们在main方法里没用到入参但签名的String[] args依然是一个合法的引用类型参数。locals4局部变量表分配了 4 个 Slot槽位。stack2操作数栈的最大深度为 2。无论后续指令怎么折腾栈里最多同时容纳 2 个计算单元。在方法被线程调用的那一瞬间JVM 为其创建的栈帧初始状态如下图所示初始栈帧结构指令执行前操作数栈max_stack2栈顶空栈底空局部变量表locals4Slot 0: args入参引用已就位Slot 1: 未分配准备留给 aSlot 2: 未分配准备留给 bSlot 3: 未分配准备留给 c注意 Slot 0 的位置。因为main是一个static方法没有隐藏的this引用所以入参args独占了第 0 号槽位。接下来的局部变量a、b、c顺理成章地瓜分了 1、2、3 号槽位。此时操作数栈空空如也程序计数器指针正对着偏移量为 0 的位置。一切准备就绪解释器的取指执行循环正式启动。三、数据是怎样在栈和表之间跳跃的整个执行过程可以分成三个清晰的阶段变量初始化赋值、数据提取与求和计算、结果保存与方法返回。3.1 赋值阶段为什么存个变量非要先入栈先看前两行指令它们负责完成int a 10;0: bipush 10 2: istore_1执行第 0 行时程序计数器读取到bipush 10。bipush是 byte immediate push 的缩写。因为常量 10 处于单字节带符号整数范围-128 到 127之内JVM 会把这个 byte 扩展为一个标准的 32 位 int 类型并直接压入操作数栈栈顶。此时栈里有了第一个数字10栈深度变为 1。紧接着程序计数器跳到偏移量为 2 的位置执行istore_1。istore_1是一条专门针对 1 号槽位做过体积优化的单字节指令。它的作用非常干脆把当前操作数栈栈顶的 int 整数弹出来直接写入局部变量表的第 1 号 Slot。局部变量表 (Slot 1)操作数栈程序计数器局部变量表 (Slot 1)操作数栈程序计数器PC 0栈深变为 1栈顶元素 10PC 2操作数栈重新恢复为空bipush 10将常数 10 压入栈顶1istore_1弹出栈顶数据2将 10 写入 Slot 1 (变量 a)3做完这一步栈顶的 10 被移走了栈重新变回空的而局部变量表 Slot 1 里正式拥有了数值 10。接下来执行的第 3 行和第 5 行3: bipush 20 5: istore_2逻辑完全如出一辙。先用bipush 20把常量 20 压入操作数栈再通过istore_2把它弹出来稳稳存进局部变量表的第 2 号 Slot。到这里代码里的前两行变量定义全部执行完毕。局部变量表里整整齐齐地躺着args、10和20而操作数栈依然是空的。3.2 计算阶段加法指令是怎样把栈顶清空的有了数据之后代码迎来了最核心的运算int c a b;。直觉上既然两个数字已经在局部变量表里放好了为什么解释器不能直接拿 Slot 1 和 Slot 2 的值相加前面提过JVM 的设计原则决定了所有算法逻辑单元都不能越过操作数栈去操作内存。要算加法必须先把参与计算的原材料重新搬出来。于是解释器执行了接下来两条指令6: iload_1 7: iload_2iload_1读取局部变量表 Slot 1 里的值也就是变量 a 的 10复制一份压入操作数栈。此时栈深为 1。iload_2读取局部变量表 Slot 2 里的值也就是变量 b 的 20同样复制一份压入操作数栈。此时栈深为 2。注意看在执行完iload_2的这一瞬间操作数栈里同时存在 10在栈底和 20在栈顶栈深正好达到了编译期预设的上限值stack2。执行 8: iadd执行 7: iload_2复制值弹出 20 与 10 求和写回结果执行 6: iload_1复制值Slot 1: 10栈深 1: [10]Slot 2: 20栈深 2: [20 (栈顶), 10]加法运算10 20 30栈深 1: [30 (栈顶)]原材料全部到位程序计数器推进到偏移量 8迎来关键指令iadd。iadd动手时毫不犹豫从操作数栈顶连续弹出两个 int 整数先弹出 20后弹出 10。交给执行引擎的算术逻辑部件完成物理加法算出结果 30。把算出来的最终结果 30 重新压回操作数栈。原先栈里的两个操作数瞬间消失取而代之的是崭新的计算结果 30栈深重新降回 1。3.3 归仓阶段计算结果如何落进目标槽位现在加法虽然算完了但变量c还没拿到值因为 30 还悬在操作数栈里。最后一步收尾顺理成章9: istore_3解释器执行istore_3把栈顶的 30 弹出直接塞进局部变量表的第 3 号 Slot。至此局部变量表 4 个槽位全员满编Slot 0args引用Slot 110变量 aSlot 220变量 bSlot 330变量 c操作数栈再次清空整个方法的数据流转全部执行完毕。为了方便直观对比我们可以把这 8 条指令的执行全貌汇成一张表指令偏移字节码指令程序计数器行为操作数栈状态从栈底到栈顶局部变量表核心变化0bipush 10PC 指向 0读取 1 字节参数[10]Slot 1 仍为空2istore_1PC 跳到 2执行单字节存表[]空Slot 1 写入 103bipush 20PC 跳到 3读取 1 字节参数[20]Slot 2 仍为空5istore_2PC 跳到 5执行单字节存表[]空Slot 2 写入 206iload_1PC 跳到 6读取 Slot 1[10]局部变量表只读不变7iload_2PC 跳到 7读取 Slot 2[10, 20]达到最大栈深 2局部变量表只读不变8iaddPC 跳到 8执行两数相加[30]消耗两个推入一个局部变量表只读不变9istore_3PC 跳到 9弹出结果落袋[]空Slot 3 写入 30四、执行到return时栈帧是怎样瞬间消失的算完加法后第 10 行指令是return10: return因为main方法声明为void返回值所以这里的return指令不需要像ireturn那样把计算结果推到调用者的操作数栈上。在很多刚接触 JVM 的同学印象中既然变量 a、b、c 都分配了内存方法结束之后是不是需要垃圾回收器GC来把这几个变量清理掉答案是根本不需要GC 连看都不会看它们一眼。因为这 4 个变量全都在栈帧内部而栈帧是直接分配在线程私有的虚拟机栈上的。当解释器执行到return指令时JVM 只做了一件非常轻量的事情出栈Pop Frame。当前 main 栈帧 (a, b, c)调用者栈帧线程虚拟机栈当前 main 栈帧 (a, b, c)调用者栈帧线程虚拟机栈当前栈顶指针指向 main 栈帧局部变量表与操作数栈内存瞬间作废执行 10: return 指令1栈帧弹出销毁Pop Frame2栈顶指针回退恢复调用者上下文3所谓栈帧销毁在底层只是让线程的栈顶指针往回移动了一个栈帧的偏移量。一旦指针后移原先分配给main方法的局部变量表、操作数栈所占用的那块栈内存在逻辑上就已经不复存在了。下次再调用新方法时新栈帧会直接覆盖这块连续的内存区域。整个销毁过程耗时只有O ( 1 ) O(1)O(1)干净利落完全不产生内存碎片更不需要垃圾收集线程跑过来扫描标记。这就是为什么无论系统高并发调用多少次方法虚拟机栈自身永远不需要 GC 的根本原因。五、写在最后回过头来看这 8 条看似笨拙的字节码指令JVM 解释器的设计哲学其实非常务实它宁愿在内存和寄存器之间多走几趟“数据倒腾”也不愿意把任何一条算术指令和底层的硬件寄存器深度绑定。正是这种看似牺牲局部效率的抽象设计换来了 Java 生态二十多年来在各种服务器、芯片架构之间如履平地的跨平台自由。如果你觉得本文把执行引擎的指令链路讲透了记得点个关注。后面我们会顺着执行引擎继续深挖 JIT 即时编译器的逃逸分析与热点探测看 JVM 是如何把这些看似繁琐的字节码指令在运行时优化成极致原生机器码的。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Nginx应用与运维——Nginx概述 2026/10/1 22:00:52

Nginx应用与运维——Nginx概述

Nginx概述1、Nginx的不同版本1.1、开源版Nginx1.2、商业版Nginx Plus1.3、分支版本Tengine1.4、扩展版本OpenResty2、Nginx源码架构浅析2.1、多进程模型2.1.1、信号2.1.2、频道2.1.3、共享内存2.1.4、进程调度2.1.5、事件驱动2.2、工作流机制2.2.1、HTTP请求处理阶段2.2.2、TCP…

阅读更多 →
让GPT当美术总监:用提示词定义3D游戏美术风格与决策流程 2026/10/1 22:00:45

让GPT当美术总监:用提示词定义3D游戏美术风格与决策流程

之前做一个小众的3D解谜项目,团队里没有专职美术,开发节奏又等不起外聘。玩法原型跑了两个月,美术方向还在“大家翻参考图翻到吵架”的阶段。我后来做了一个比较大胆的决定:让GPT来当这个项目的“美术总监”,专门负责定…

阅读更多 →
CS-Base 图解计算机基础:图解网络、图解系统、图解 MySQL、图解 Redis 知识库全览 2026/10/1 22:00:45

CS-Base 图解计算机基础:图解网络、图解系统、图解 MySQL、图解 Redis 知识库全览

文档教程知识库 【免费下载链接】CS-Base 图解计算机网络、操作系统、计算机组成、数据库,共 1000 张图 50 万字,破除晦涩难懂的计算机基础知识,让天下没有难懂的八股文!🚀 在线阅读:https://xiaolincodin…

阅读更多 →
uniapp自定义弹窗组件方案:替代uni.showModal的多端一致实践 2026/10/1 22:00:39

uniapp自定义弹窗组件方案:替代uni.showModal的多端一致实践

做跨端开发的朋友应该都遇到过这个尴尬场景:uniapp 里自带的uni.showModal确实能弹出确定/取消框,但样式上基本没什么可改的,改个按钮颜色已经是极限了。更难受的是同一套代码跑到 App、小程序、H5 上,弹窗长相还不一样&#xff0…

阅读更多 →
多片一致性架构解析:Intel与ARM的缓存协同策略与工业实践 2026/10/1 22:00:39

多片一致性架构解析:Intel与ARM的缓存协同策略与工业实践

前两年我调一个双路服务器的工业控制器,遇到一个非常诡异的延迟抖动。任务没有超时,也没有锁竞争,但每跑几分钟就跳出一个毫秒级尖峰,触发看门狗告警。最终定位到一块跨NUMA节点的共享数据,被多片一致性架构里的目录协…

阅读更多 →
Python语音对话系统开发:从基础到实践的完整指南 2026/10/1 22:00:32

Python语音对话系统开发:从基础到实践的完整指南

一、我们来看看那个能够进行声音和文字来回交流的系统的整体的组织结构, 以及它里面那些最关键的部分。语音对话系统这个事儿, 在开发的时候, 要抓好三个最核心的环节。第一个叫语音识别, 也就是把音频内容转化成文字的形式。第二个是自然语言处理, 这一步得让机器能够看懂文本…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉