Class 文件是 JVM 的可执行输入,不只是“编译后的二进制”。本教程带你编译一个小类,并用 JDK 自带的 javap 把
常量池、方法描述符和字节码逐项对应到源码。
Table of contents
Open Table of contents
准备并编译样例
下载字节码样例下载, 在实验室根目录运行:
mvn clean compile
javap -classpath target/classes -c -p -s -v dev.elaine.jvmlab.BytecodeSample > bytecode.txt
打开 bytecode.txt,先确认文件头:
magic: 0xCAFEBABE
major version: 65
Java 21 的 Class 主版本是 65;Java 25 是 69。旧 JVM 遇到更高版本会抛出 UnsupportedClassVersionError。修复方法是
使用匹配运行时,或用 --release 重新编译到目标版本,而不是手工改 Class 文件版本号。
读取常量池
**常量池(Constant Pool)**保存数字、字符串、类名,以及字段和方法的符号引用。字节码通常通过索引引用这些项:
#7 = Methodref ... java/io/PrintStream.println:(I)V
(I)V 是方法描述符:接收一个 int,返回 void。符号引用在加载和链接过程中解析为运行时可用结构。常量池不等于
字符串驻留池,两者用途和生命周期不同。
手工走一遍求和字节码
sumTo(int) 的关键指令类似:
iconst_0
istore_1
iconst_1
istore_2
iload_2
iload_0
if_icmpgt ...
iinc 1, 1
iinc 2, 1
goto ...
iload_1
ireturn
局部变量槽 0 保存参数 upperBound,槽 1 保存 total,槽 2 保存循环变量。iload 把值压入操作数栈,比较指令弹出
两个值,ireturn 返回栈顶整数。
字节码是基于栈的指令集,不直接对应 CPU 寄存器。解释器执行它,JIT 也可以把热点路径改写成完全不同的机器码。
使用字节码定位问题
字节码分析适合确认这些问题:
- 编译器是否生成了装箱、桥接方法或
invokedynamic。 - 某个调用是
invokevirtual、invokeinterface还是invokestatic。 - 依赖是否使用了高于生产 JVM 的 Class 版本。
- Kotlin、Scala 或 Lombok 等工具最终生成了什么结构。
Java 25 的Class 文件规范是字段含义的权威来源。
下一步
阅读解释执行、JIT、分层编译与反优化,观察字节码如何变成热点 机器码。