当前位置: 首页 > news >正文

手把手看懂 Java 字节码:讲透 Integer 判等、静态方法重写与 try-finally 核心底层

前言

日常业务开发中,我遇到了一个很有意思的现象,先看这段极简代码:

Integer i1 = 128; Integer i2 = 128; System.out.println(i1 == i2);

运行输出结果为:false

初次看到这个结果时其实挺疑惑的,明明都是赋值 128,为什么用==判断却不相等?翻阅资料后才发现,这背后本质是 Integer 底层机制在起作用。

顺着这个问题往下深挖,就绕不开 Class 文件、字节码指令与包装类缓存的核心原理。下面我们就从根源入手,一步步拆解底层逻辑。


一、Class文件结构

JVM 只遵守一条规则:只认符合标准的 Class 文件。只要你能生成符合该规范的 Class 文件,无论其来源如何,都能在任意兼容的 JVM 上运行。JVM 本身并不关心 Class 文件是由 Java、Kotlin 还是其他语言编译而来 —— 这正是 JVM 能够跨语言支持的底层基础。

1、🌰🌰举个例子

package com.nl; public class ClassDemo { public static void main(String[] args) { Integer i3 = 128; Integer i4 = 128; System.out.println(i3 == i4); } }

2、Class 文件

2.1、Class 文件本质上是一个纯二进制文件

它无法直接用普通文本编辑器阅读,但可以用十六进制工具(如 Sublime Text 等)打开查看

2.2、jclasslib 插件

不过,盯着这一串十六进制数字看,理解起来还是太抽象了。我们可以借助 IDEA 里的jclasslib 插件,它能把 Class 文件的二进制内容,转换成清晰的结构化视图,效果大概是这样的:

2.3、LineNumberTable字节码对应代码表

⚠️LineNumberTable就是字节码指令与 Java 源代码行号的映射表,它的作用是:
✔️让 JVM 在调试、异常堆栈时,能精准把字节码位置对应到源代码的第几行,方便定位问题。

3、字节码解析

0 sipush 128 // 将常量128压入操作数栈 3 invokestatic #2 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;> // 调用Integer.valueOf(128),返回Integer对象 6 astore_1 // 将返回的Integer对象存储到局部变量表索引1的位置(即变量a) 7 sipush 128 // 将常量128压入操作数栈 10 invokestatic #2 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;> // 调用Integer.valueOf(128),返回Integer对象 13 astore_2 // 将返回的Integer对象存储到局部变量表索引2的位置(即变量b) 14 getstatic #3 <java/lang/System.out : Ljava/io/PrintStream;> // 获取System.out静态字段(PrintStream对象) 17 aload_1 // 将局部变量a(Integer对象引用)压入栈 18 aload_2 // 将局部变量b(Integer对象引用)压入栈 19 if_acmpne 26 // 如果两个引用不相等,跳转到第26行 22 iconst_1 // 将常量1压入栈(表示true) 23 goto 27 // 跳转到第27行 26 iconst_0 // 将常量0压入栈(表示false)【跳转目标】 27 invokevirtual #4 <java/io/PrintStream.println : (Z)V> // 调用println(boolean)方法输出结果 30 return // 方法返回
⚠️注意
✔️字节码第 0~7 行,对应源码中的Integer i3 = 128
✔️ 从字节码中可以清晰看出:代码底层会自动调用Integer.valueOf(128)完成装箱赋值,最终返回 Integer 包装类对象。
⚠️是javap 反汇编后的字节码指令,工具把二进制解析后翻译成人读:
✔️把 0x11 这种机器码 → 翻译成 sipush
✔️把 0x36 → 翻译成 astore_1
✔️把索引 #2 → 翻译成 Integer.valueOf

二、面试题

1、明明两个数的值一模一样,为什么 == 比较后返回的是 false?

在上文的《一、Class文件结构》 的例子,查看字节码,发现这里的赋值本质上调用了Integer.valueOf()方法,而它背后的对象缓存机制,才是导致结果差异的关键:

public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
  • 当你写Integer i = 128;时,编译器会自动装箱成Integer.valueOf(128)
  • Integer.valueOf()会对-128 ~ 127之间的整数做缓存,超出这个范围会新建对象。
  • 所以127时两个对象是同一个,==true128时是两个不同对象,==false

2、静态方法为什么不能被重写Override

2.1、举个例子

ClassTwoDemo

package com.nl; public class ClassTwoDemo { public void execute() { test(); ClassTwoDemo.testTwo(); } public void test() { System.out.println("ClassTwoDemo.test()"); } public static void testTwo() { System.out.println("ClassTwoDemo.testTwo()"); } }

ClassThreeDemo

package com.nl; public class ClassThreeDemo extends ClassTwoDemo { @Override public void test() { System.out.println("ClassTwoDemo.testTwo()"); } /** * 这个是报错的,静态方法不能被重写 */ @Override public void testTwo() { System.out.println("ClassTwoDemo.testTwo()"); } }
⚠️注意
✔️java: com.nl.ClassThreeDemo中的testTwo()无法覆盖com.nl.ClassTwoDemo中的testTwo()被覆盖的方法为static

2.2、为什么会报错呢?

execute方法的字节码

0 aload_0 1 invokevirtual #2 <com/nl/ClassTwoDemo.test : ()V> 4 invokestatic #3 <com/nl/ClassTwoDemo.testTwo : ()V> 7 return
⚠️注意
✔️从 JVM 字节码指令来看,invokevirtualinvokestatic作用不同,静态方法和普通重载方法的调用指令并不一样

查阅相关资料可知,JVM 提供了多条方法调用字节码指令,各自分工不同

  • invokevirtual:调用对象的虚方法,也是日常中支持重写特性的实例方法;
  • invokespecial:依据编译时类型绑定调用实例方法,常用于构造方法<init>、私有方法、父类方法调用,并不负责静态代码块;
  • invokestatic:专门调用类静态方法,不支持重写特性的实例方法
  • invokeinterface:用于调用接口中的抽象方法。

2.3、结论

静态方法底层固定使用invokestatic指令,仅做静态绑定、不具备动态绑定能力,故而语法上不允许被重写。

3、讲讲字节码try-cache-finally的执行流程?

3.1、举个例子

package com.nl; public class ClassFlourDemo { public int execute() { try { return 1; } catch (Exception e) { return 2; } finally { end(); } } private void end(){ } }

3.2、字节码

0 iconst_1 // 将常量1压入操作数栈 1 istore_1 // 存储到局部变量表索引1(暂存返回值) 2 aload_0 // 加载this引用(当前对象) 3 invokespecial #2 <com/nl/ClassFlourDemo.end : ()V> // 调用 this.end() ← finally块执行 6 iload_1 // 加载暂存的返回值1 7 ireturn // 返回1 8 astore_1 // 将异常引用存储到局部变量表索引1(即变量e) 9 iconst_2 // 将常量2压入操作数栈 10 istore_2 // 存储到局部变量表索引2(暂存返回值) 11 aload_0 // 加载this引用 12 invokespecial #2 <com/nl/ClassFlourDemo.end : ()V> // 调用 this.end() ← finally块执行 15 iload_2 // 加载暂存的返回值2 16 ireturn // 返回2 17 astore_3 // 将异常引用存储到局部变量表索引3 18 aload_0 // 加载this引用 19 invokespecial #2 <com/nl/ClassFlourDemo.end : ()V> // 调用 this.end() ← finally块执行 22 aload_3 // 加载异常引用 23 athrow // 重新抛出异常

3.3、finally块的"复制"机制

invokespecial #2 <com/nl/ClassFlourDemo.end : ()V>
⚠️注意
✔️这条指令出现了 3次(第3行、第12行、第19行),说明编译器将 finally 块复制到了每个可能的退出路径上。

4、什么是返回值的暂存机制?

// 不是直接返回,而是: // 1. istore_2 暂存返回值(1) // 2. 执行finally (x被改成999) // 3. iload_2 加载暂存的返回值(1) // 4. ireturn 真正返回(1) public int test() { int x = 1; try { return x; } finally { x = 999; // 不会影响返回值 } }
⚠️为什么需要暂存?
✔️因为 finally 可能在return之前修改数据
✔️Java保证:finally中的代码不会改变已经确定的返回值(对于基本类型)

三、总结

本文通过字节码,通俗易懂拆解了三个经典Java问题。

  • 借助jclasslib插件能直观看到,Integer自动装箱会调用valueOf方法,缓存机制导致判等结果出人意料;
  • 静态方法采用invokestatic静态绑定,没有动态绑定能力,所以不能重写;
  • 而try-finally语句,编译器会把finally代码复制到每一处退出路径,同时暂存返回值。

很多看似反常的代码现象,其实都能在字节码里找到答案。看懂字节码,不仅能轻松拿捏面试高频题,还能搞懂JVM底层运行逻辑,不用死记硬背,真正吃透Java底层知识。

http://www.jsqmd.com/news/819189/

相关文章:

  • 开发者如何构建高效个人知识库:从碎片化到系统化的全栈实践
  • ServerSlayer:一站式服务器性能压测与基准测试工具实战指南
  • 2026苏州304法兰技术解析与权威选型参考指南:苏州不锈钢风管、苏州共板法兰、苏州异形法兰、苏州法兰接头、苏州焊接风管选择指南 - 优质品牌商家
  • Is This A Dream?(纯属OFIRM科幻虚构,切勿当真!!!) 5元AGI:人类文明的终极奇点与瞬间重构
  • 混合整数非线性规划的认证预测器方法与实践
  • AI Agent vs RPA/脚本自动化:5个维度数据对比揭示2024年企业自动化升级的生死分水岭
  • 2026非开挖修复厂家选择指南:非开挖修复公司、非开挖修复内衬管道、非开挖修复厂家、非开挖固化修复、cipp管道非开挖修复选择指南 - 优质品牌商家
  • ARMv8/v9架构中MDCR_EL3寄存器调试功能详解
  • 2026宜宾全屋定制工厂直营排行:宜宾高端全屋定制、餐边柜定制、高端柜子定制、F4星/ENF级环保板材、书房定制选择指南 - 优质品牌商家
  • 2026年格栅选型技术指南:锌钢铝合金百叶窗、防雨百叶窗、不锈钢百叶窗、手动百叶窗、焊接格栅、空调百叶窗、空调铝合金格栅选择指南 - 优质品牌商家
  • 2026年电焊网工厂哪家强?市场口碑大揭秘
  • 自进化AI智能体:从核心架构到工程实践
  • 2026年新趋势:江苏优质家用健身器材实力厂商深度解析与推荐 - 2026年企业推荐榜
  • LaTeX2Word-Equation:终极开源方案实现LaTeX公式到Word的一键智能转换
  • AI提示词模板引擎:告别字符串拼接,高效管理LLM上下文
  • 口碑好的大连会议生产厂家
  • WeChatExporter终极指南:3步轻松备份你的微信聊天记录
  • 企业AI如何开发:智能体时代技术团队的角色重塑与能力升级
  • 大华NVR通道与硬盘信息批量获取
  • ARM架构TLB失效指令详解与应用实践
  • 如何快速清理电脑中的重复图片:AntiDupl免费开源图片去重工具完全指南
  • 如何高效使用Figma中文界面插件:设计师必备的实用工具指南
  • 2026靠谱小型叉车厂家名录:工程机械配件/附近工程机械维修/AGV叉车/内燃叉车/叉车价格/周边工程机械大修/选择指南 - 优质品牌商家
  • HIL测试效率翻倍秘籍:玩转ControlDeskNG和AutomationDesk的自动化脚本
  • 如何轻松提取Wallpaper Engine壁纸资源:RePKG完整实用指南
  • 探索锌钢护栏领域,揭秘口碑载道的厂商背后的故事
  • 保姆级教程:在ROS Noetic上从零实现Pure Pursuit纯跟踪算法(附完整代码)
  • WeChatExporter:一键备份微信聊天记录,让珍贵回忆永不丢失
  • AgentPort:AI智能体服务化框架的设计原理与生产实践
  • IC设计中的并行时序分析技术与优化实践