JVM规范第 3 章:从 Java 源码到字节码
本教程基于 Oracle 官方《The Java Virtual Machine Specification》Java SE 26第 3 章《Compiling for the Java Virtual Machine》整理编写。这一章本身不是规范性内容而是由 Oracle 的javac示例展示「Java 源码是怎么变成 JVM 指令的」。学完这篇你再看到javap -c反编译出来的字节码就不会一头雾水了。前置知识机器上装了 JDK需要javac和javap知道「栈后进先出」是什么一、先学会看「虚拟机汇编」示例都是「Java 源码 编译后的字节码清单」成对出现。字节码用javap输出的「虚拟机汇编语言」表示每行格式是偏移 操作码 [ 操作数1 [ 操作数2... ]] [// 注释]例如8 bipush 100 // Push int constant 1008这条指令在方法字节码数组里的偏移量bipush**操作码opcode**助记符100操作数后面是注释运行时常量池的引用用#前缀10 ldc #1 // 从常量池 #1 推入 float 常量 9 invokevirtual #4 // 调用 Example.addTwo(II)I后面所有示例都遵循这个格式。先看偏移、再看 opcode、最后看注释很快就能形成直觉。二、常量、局部变量与控制结构JVM 是「栈机器」最重要的心智模型JVM 是基于栈的。大多数操作从「当前帧的操作数栈」弹出操作数、算完再把结果压回去。方法被调用时会新建一个帧自带一个操作数栈和一组局部变量。2.1 一个for循环在字节码里长什么样voidspin(){inti;for(i0;i100;i){;// 循环体为空}}编译为0 iconst_0 // 把 int 常量 0 压栈 1 istore_1 // 存到局部变量 1i 0 2 goto 8 // 第一次先跳过自增 5 iinc 1 1 // 局部变量 1 自增 1i 8 iload_1 // 把局部变量 1 压栈i 9 bipush 100 // 把 int 常量 100 压栈 11 if_icmplt 5 // 若 i 100跳回 5 继续 14 return // 否则返回观察几个细节常量0用iconst_0带「隐式操作数」的专用指令100用bipush单字节立即数i是int存在局部变量槽 1istore_1/iload_1循环条件i 100被编译成「压栈 i → 压栈 100 →if_icmplt比较跳转」2.2double循环没有dincvoiddspin(){doublei;for(i0.0;i100.0;i){;}}关键片段5 dload_1 6 dconst_1 7 dadd // 用 dadd 做自增因为【没有 dinc 指令】 8 dstore_1 ... 13 dcmpg // 浮点比较用 dcmpg 14 iflt 5 // 配合 iflt没有 if_dcmpltdouble占两个局部变量槽自增不能像int那样iinc只能用dloaddconst_1dadddstore浮点比较没有「带条件的比较跳转」而是dcmpg先比较、再配合iflt这类通用跳转2.3short循环i2s截断voidsspin(){shorti;for(i0;i100;i){;}}5 iload_1 6 iconst_1 7 iadd 8 i2s // 把 int 截断回 short 9 istore_1 ...规范早就说过byte/char/short在 JVM 里都是当int算的之前第 2 章讲过。所以自增在int上做完再用i2s截断回short存回去。三、算术位运算的「另类写法」intalign2grain(inti,intgrain){return((igrain-1)~(grain-1));}0 iload_1 // i 1 iload_2 // grain 2 iadd // i grain 3 iconst_1 4 isub // ... - 1 5 iload_2 // grain 6 iconst_1 7 isub // grain - 1 8 iconst_m1 // -1 9 ixor // ~(grain-1) 用 (-1) XOR (grain-1) 实现 10 iand // 与上一步结果 11 ireturn注意~x按位取反在字节码里是用-1 XOR xiconst_m1ixor实现的——这是编译器常用技巧。四、访问运行时常量池小常量走「快车道」把不同大小的常量推入栈JVM 给了不同的指令常量类型指令小int-1、0、1、2、3、4、5iconst_i如iconst_0单字节intbipush两字节intsipush大int/float/Stringldc/ldc_w从常量池取long/doublelconst_l/dconst_d、ldc2_w示例voiduseManyNumeric(){inti100;intj1000000;longl11;longl20xffffffff;// 即 int 的 -1doubled2.2;}0 bipush 100 // 小 int 用 bipush 2 istore_1 3 ldc #1 // 大 int 用 ldc从常量池 #1 取 1000000 5 istore_2 6 lconst_1 // 很小的 long 用 lconst_1 7 lstore_3 8 ldc2_w #6 // long 0xffffffff 用 ldc2_w 11 lstore 5 13 ldc2_w #8 // double 2.2 用 ldc2_w 16 dstore 7经验编译器会「能省则省」——够小的常量用专用短指令大了才往常量池塞。五、更多控制流while循环voidwhileInt(){inti0;while(i100){i;}}0 iconst_0 1 istore_1 2 goto 8 // 先跳到条件判断避免首轮多自增一次 5 iinc 1 1 // 循环体i 8 iload_1 9 bipush 100 11 if_icmplt 5 // 条件成立跳回 5 14 returnwhile和for编译出来几乎一样——测试在底部goto先跳过首轮自增。六、接收参数this占的槽位很重要的一条规则实例方法参数从局部变量槽1开始槽0永远是this静态方法没有this参数从槽0开始intaddTwo(inti,intj){returnij;}// 实例方法staticintaddTwoStatic(inti,intj){returnij;}// 静态方法Method int addTwo(int,int) 0 iload_1 // 实例方法第 1 个参数在槽 1 1 iload_2 // 第 2 个参数在槽 2 2 iadd 3 ireturn Method int addTwoStatic(int,int) 0 iload_0 // 静态方法第 1 个参数在槽 0 1 iload_1 2 iadd 3 ireturn七、方法调用四条invoke*指令指令用途特点invokevirtual实例虚方法操作数指向常量池里的符号引用invokestatic静态方法不传thisinvokespecial父类方法、构造器init跳过虚分派invokeinterface接口方法本例未展开调用实例方法注意要先aload_0把this压栈intadd12and13(){returnaddTwo(12,13);}0 aload_0 // 压入 this 1 bipush 12 3 bipush 13 5 invokevirtual #4 // Method Example.addTwo(II)I 8 ireturn调用父类方法用invokespecialclassFarextendsNear{intgetItFar(){returnsuper.getItNear();}}0 aload_0 1 invokespecial #4 // Method Near.getItNear()I不虚分派 4 ireturn八、对象与字段new必须配invokespecial init创建一个对象的标准三步Objectcreate(){returnnewObject();}0 new #1 // 1) 在堆上创建实例引用压栈 3 dup // 2) 复制栈顶引用一份给 init一份留给返回 4 invokespecial #4 // 3) 调用 init 初始化注意不是 invokevirtual 7 areturn // 返回栈上那个引用dup是关键init会「消费」一份引用但方法最终要areturn把新对象返回所以需要两份。字段读写用getfield/putfield都通过常量池符号引用定位voidsetIt(intvalue){ivalue;}intgetIt(){returni;}Method void setIt(int) 0 aload_0 // this 1 iload_1 // value 2 putfield #4 // Field Example.i I 5 return Method int getIt() 0 aload_0 1 getfield #4 4 ireturn九、数组newarray/anewarray/multianewarrayvoidcreateBuffer(){intbuffer[];intbufsz100;intvalue12;buffernewint[bufsz];buffer[10]value;valuebuffer[11];}6 iload_2 // bufsz 7 newarray int // 创建 int[]基本类型数组用 newarray 9 astore_1 10 aload_1 11 bipush 10 13 iload_3 14 iastore // buffer[10] value 15 aload_1 16 bipush 11 18 iaload // value buffer[11] 19 istore_3多维数组用multianewarrayint[][][]create3DArray(){intgrid[][][];gridnewint[10][5][];returngrid;}0 bipush 10 2 iconst_5 3 multianewarray #1 dim #2 // 前两维是 10 和 5第三维留 null 7 astore_1 8 aload_1 9 areturn十、switch密集用tableswitch稀疏用lookupswitchswitch只支持int其它类型编译期会被提升为int。密集连续的 case →tableswitchintchooseNear(inti){switch(i){case0:return0;case1:return1;case2:return2;default:return-1;}}0 iload_1 1 tableswitch 0 to 2: 0: 28 1: 30 2: 32 default:34 28 iconst_0 29 ireturn 30 iconst_1 ...稀疏分散的 case →lookupswitch表必须按 key 排序intchooseFar(inti){switch(i){case-100:return-1;case0:return0;case100:return1;default:return-1;}}0 iload_1 1 lookupswitch 3: -100: 36 0: 38 100: 40 default: 42直觉tableswitch像「用 case 值当下标直接跳」lookupswitch像「查排序表做二分」。编译器按 case 是否密集自动选。十一、操作数栈操作dup2_x1等栈操作指令把栈上的值当「无类型」处理但不会拆开一个long/double它们占两个单元。publiclongnextIndex(){returnindex;}privatelongindex0;0 aload_0 1 dup 2 getfield #4 // 取出当前 index 5 dup2_x1 // 复制并插入栈下方为「返回旧值」和「自增后写回」做准备 6 lconst_1 7 ladd 8 putfield #4 // index index 1 写回 11 lreturn // 返回旧值十二、异常throw与try-catchthrow new X()编译成「newdupinvokespecial initathrow」voidcantBeZero(inti)throwsTestExc{if(i0){thrownewTestExc();}}0 iload_1 1 ifne 12 // 若 i ! 0 跳过 4 new #1 // new TestExc 7 dup 8 invokespecial #7 // init 11 athrow // 抛出 12 returntry-catch不靠特殊跳转而是靠异常表Exception tablevoidcatchOne(){try{tryItOut();}catch(TestExce){handleExc(e);}}0 aload_0 1 invokevirtual #6 // tryItOut() 4 return 5 astore_1 // 若抛出异常跳到这里e 异常对象 6 aload_0 7 aload_1 8 invokevirtual #5 // handleExc(e) 11 return Exception table: From To Target Type 0 4 5 Class TestExc异常表含义在偏移0~4含头不含尾之间若抛出TestExc就跳到偏移5开始处理。多 catch、嵌套 catch 都是用多条异常表项表达的。十三、finallyjsr/ret子例程旧版本注这是 class 文件版本 ≤ 50.0即 Java 7 及以前的做法。现代 Java 用StackMapTable 复制 finally 代码块的方式不再用jsr/ret还记得第 2 章说returnAddress是给jsr/ret用的吗这就是它的归宿。voidtryFinally(){try{tryItOut();}finally{wrapItUp();}}0 aload_0 1 invokevirtual #6 // tryItOut() 4 jsr 14 // 正常路径跳去执行 finally 7 return 8 astore_1 // 异常路径异常存起来 9 jsr 14 // 同样去执行 finally 12 aload_1 13 athrow // finally 跑完后把异常重新抛出 14 astore_2 // finally 子例程入口 15 aload_0 16 invokevirtual #5 // wrapItUp() 19 ret 2 // 用 returnAddress 返回调用点 Exception table: From To Target Type 0 4 8 any十四、同步monitorenter/monitorexitsynchronized块的编译进入时monitorenter退出时monitorexit并且编译器会保证每条退出路径都有配对的monitorexit通过异常表兜底。voidonlyMe(Foof){synchronized(f){doSomething();}}0 aload_1 1 dup 2 astore_2 // 把 f 的引用存一份供释放时用 3 monitorenter // 加锁 4 aload_0 5 invokevirtual #5 // doSomething() 8 aload_2 9 monitorexit // 正常释放 10 goto 18 13 astore_3 // 异常路径把异常存起来 14 aload_2 15 monitorexit // 异常时也释放锁关键避免死锁 16 aload_3 17 athrow // 再抛出 18 return Exception table: From To Target Type 4 10 13 any 13 16 13 any而synchronized方法更简单——不需要显式指令靠方法表中的ACC_SYNCHRONIZED标志在调用时隐式加锁。十五、注解与模块简述注解编译进 class 文件见规范 §4.7.16包级注解会编译成一个叫package-name.package-info的接口类文件。模块模块声明编译成含Module属性的module-info.class设ACC_MODULE标志。该属性列出requires/exports/opens/uses/provides除java.base外模块必须显式声明依赖java.base没写编译器会自动补一个ACC_MANDATED项。小结栈架构运算在操作数栈上完成iconst/bipush/ldc等把常量送进去控制流for/while都编译成「底部测试 goto跳过首轮」switch按密集度选tableswitch/lookupswitch方法调用invokevirtual虚/invokestatic静态/invokespecial父类/构造对象newdupinvokespecial init是固定套路字段用getfield/putfield数组newarray/anewarray/multianewarray异常thrownewdupinitathrowcatch靠异常表同步monitorenter/monitorexit 异常表兜底方法级用ACC_SYNCHRONIZED

相关新闻

菲娜带你解决“生产计划”

菲娜带你解决“生产计划”

菲娜前两个月快累死了.没时间更新.久等了.现在课外班结束.边复习边来写文章更新啦!!!!先上题.......题目描述F 工厂接到了一批新的玩具生产订单,第 i 个订单由 ai​,bi​ 描述,意为需要在第 ai​ 天之前提供…

2026/8/26 16:09:30 阅读更多 →
AI训练师图解_6.2_五大行业应用_NLP

AI训练师图解_6.2_五大行业应用_NLP

6.2 五大行业应用:NLP 文章目录 6.2 五大行业应用:NLP6.2.1 情感分析:理解文本中的情感倾向案例56 百度大脑情感倾向分析让舆论分析更直观 6.2.2 聊天机器人:模拟人类对话的智能助手案例57 字节跳动基于云雀大模型开发的AI工具—豆…

2026/8/26 15:51:20 阅读更多 →
面试高频考点 02:看门狗 / 启动模式 / 复位 / Bootloader 面试要点

面试高频考点 02:看门狗 / 启动模式 / 复位 / Bootloader 面试要点

适用人群:和我一样正在准备 2026 秋招的嵌入式方向同学——前几篇专栏把看门狗、启动流程、OTA 都讲过基础了,但面试官一追问"你项目里看门狗怎么用的"、“复位和上电有啥区别”、“Bootloader 跳转前要做什么”,还是容易卡壳 读完…

2026/8/25 14:59:36 阅读更多 →

最新新闻

HoRain云--RSS 阅读器

HoRain云--RSS 阅读器

RSS 阅读器用于读取 RSS feed! RSS 阅读器可供许多不同的设备和操作系统使用。 RSS 阅读器 有很多不同的 RSS 阅读器。某些以 web services 的形式来工作,而某些则运行于 windows (或 Mac、PDA 或 UNIX)。 这是一些我尝试过并钟…

2026/8/26 19:30:19 阅读更多 →
别再盲目买国产手操器!弄懂这点,工业调试少走弯路

别再盲目买国产手操器!弄懂这点,工业调试少走弯路

身为工业仪表运维、自动化工程从业者,大家一定深有感触:一台靠谱的 HART 通讯手操器,便是调试变送器、排查仪表故障最重要的工具。 很多经销商、现场工程师为了压缩采购成本,第一选择便是价格亲民的国产手操器。刚买回来调试新款普…

2026/8/26 19:30:19 阅读更多 →
飞算JavaAI 一键生成完整工程代码深度实战:自定义模块路径与工程化落地方案

飞算JavaAI 一键生成完整工程代码深度实战:自定义模块路径与工程化落地方案

一位架构师视角的实战手记:当团队把"一键生成完整工程代码"从"演示 demo"推进到"生产级落地"时,遇到的真正难题不是 AI 写不出代码,而是怎么让 AI 写出符合企业工程规范的代码。本文从架构师视角拆解"自定…

2026/8/26 19:30:19 阅读更多 →
黑马点评项目学习(一):短信登录模块 + 扩展(双token无感刷新)

黑马点评项目学习(一):短信登录模块 + 扩展(双token无感刷新)

黑马点评项目学习(一):短信登录模块 扩展(双token无感刷新)最近开始跟练黑马点评项目,把短信登录这一块的一些思考整理成笔记,分享给正在学习这个项目的朋友。一、项目背景与数据库设计 这是一…

2026/8/26 19:30:19 阅读更多 →
别再手动写周报了,Codex 帮你自动生成高质量总结

别再手动写周报了,Codex 帮你自动生成高质量总结

把零散工作变成专业汇报 每到周五下午,很多研发同事最头疼的不是写代码,而是写周报。脑子里装满了本周修过的 Bug、做过的 Code Review、开的架构会,但落到文档里却变成了流水账: “周一修了订单模块,周二看了新人代码…

2026/8/26 19:30:19 阅读更多 →
基于springboot2+vue3的校园网上店铺系统

基于springboot2+vue3的校园网上店铺系统

1. 代码获取 https://blog.xiaobias.com/article/10 2. 项目简介 本项目为“校园网上店铺”系统,是一个面向校园用户的电子商务平台。系统支持多角色(管理员、商铺、普通用户)登录与操作,提供商品浏览、购物车、订单管理、商品收…

2026/8/26 19:29:18 阅读更多 →

日新闻

Python random 模块常用函数详解:从入门到实战

Python random 模块常用函数详解:从入门到实战

目录 1. 引言2. 准备工作3. 基础随机函数4. 序列相关函数5. 随机种子与复现6. 实战案例7. 注意事项8. 常见问题与排查9. 总结 1. 引言 摘要: 本文系统介绍 Python 标准库 random 模块中最常用的随机数生成函数。内容涵盖基础随机函数(random()、unifor…

2026/8/26 0:00:40 阅读更多 →
《Microsoft Sql server 2008 Internals》读书笔记--第三章Databases and Database Files(2)

《Microsoft Sql server 2008 Internals》读书笔记--第三章Databases and Database Files(2)

《Microsoft Sql server 2008 Internals》索引目录: 《Microsoft Sql server 2008 Internals》读书笔记--目录索引 在上篇文章中,主要介绍了创建数据库的基本语法和FileGroup的初步知识。需要注意的是: 关于FileGroup 如果你的系统是用Raid设备直接存…

2026/8/26 1:18:18 阅读更多 →
政务AI智能体怎么建?三种模式、三步路径与四个误区

政务AI智能体怎么建?三种模式、三步路径与四个误区

政务AI智能体已经从概念试点阶段,转入了政务服务的常态化落地应用;在实际使用过程中,它能自主理解办事需求、辅助完成填报申报、开展材料预审,并联动多个系统协同作业,真正嵌入到政务办理的全流程当中。但在落地推进过…

2026/8/26 1:18:18 阅读更多 →

周新闻

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/26 14:45:33 阅读更多 →
SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/26 17:46:43 阅读更多 →
Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/26 14:46:37 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/26 3:50:20 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/26 17:46:39 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/26 1:24:05 阅读更多 →