适用读者:刚上手 Java、或"用了几年但说不清字节码"的人。本系列第一篇。
前置:S01-B02(JDK 与环境变量)、S02-B01(分层思维)。
实测环境:OpenJDK 21.0.12.1 / Ubuntu 24.04.1(javac、javap实测,2026-11-05)。
覆盖旧文:A-1836(176) A-1893(188) A-1640(40) = 404 点;A-1831(IDEA 安装)→ S01duplicate_ref。
覆盖扩展:E-S04-001(JVM/字节码/编译期)、E-S04-002(LTS 与版本矩阵)。
下一篇:S04-B02 · 基本类型与装箱。
1. 一句话结论
"一次编写,到处运行"不是靠解释执行,而是靠"编译成中间语言(字节码)+ 每个平台各有一个 JVM"。
源码 .java ──javac──► 字节码 .class ──JVM(各平台各一份)──► 机器码(JIT 编译)
│
└─ 同一份 .class 在 Linux/Windows/macOS 都能跑
| 阶段 | 谁来做 | 产物 |
|---|---|---|
| 编译期 | javac |
.class(字节码 + 常量池 + 符号引用) |
| 类加载 | ClassLoader | 方法区中的运行时结构 |
| 运行期 | JVM(解释 + JIT) | 机器码(热点代码被编译) |
2. 编译期发生了什么(实测)
$ javac Box.java
$ javap -c Box | head -20
实测输出(节选):
public static void main(java.lang.String[]);
Code:
0: bipush 127
2: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: bipush 127
8: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
12: sipush 128
15: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
三条可以直接读出来的信息:
| 字节码 | 含义 |
|---|---|
bipush 127 / sipush 128 |
常量入栈(127 用单字节指令,128 需要双字节) |
invokestatic Integer.valueOf |
自动装箱发生在编译期插入的 valueOf 调用(B02 的关键) |
astore_1 |
存到局部变量槽 1 |
这说明:"装箱"不是运行时的魔法,是编译器替你插了一段 valueOf。看懂这一点,B02 的 == 问题就不再玄学。
3. 版本:为什么必须谈 LTS(E-S04-002)
| 版本 | 状态 | 关键特性 |
|---|---|---|
| 8 | 老项目主力 | Lambda、Stream、Optional |
| 11 | LTS | var、标准 HTTP Client |
| 17 | LTS(推荐基线) | sealed、record(预览转正)、强封装 JDK 内部 |
| 21 | LTS(新项目推荐) | 虚拟线程、switch 模式匹配、分代 ZGC |
| 非 LTS(如 18/19/20) | 6 个月即止 | 不要用于生产 |
判据:团队基线选 17 或 21;升不升看 §A01 的检查单(依赖兼容、构建工具、容器镜像)。
4. 一个最小可运行例子
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, S04");
}
}
$ javac Hello.java && java Hello
Hello, S04
| 命令 | 作用 |
|---|---|
javac |
编译(需要 JDK) |
java |
启动 JVM 并运行(只需要 JRE) |
javap |
反汇编字节码(排障神器) |
"javac: command not found" 说明只有 JRE;"UnsupportedClassVersionError" 说明编译用高版本、运行用低版本(S01-B02 §2)。
5. 五个误区
| 误区 | 真相 |
|---|---|
| "Java 是解释型语言" | 先编译成字节码,热点再 JIT 编译成机器码 |
| "字节码等于机器码" | 字节码是 JVM 指令集,与 CPU 无关 |
| "装 JRE 就能编译" | 编译需要 JDK(含 javac) |
| "版本越高越好" | 生产用 LTS;非 LTS 半年停更 |
"javap 只是炫技" |
排查"为什么装箱/为什么这里有同步"时最直接 |
6. 自检
- "一次编写到处运行"依赖哪两件事?
javap -c里invokestatic Integer.valueOf说明了什么?- JDK 与 JRE 的区别?只装 JRE 会缺什么?
- 为什么生产环境不建议用非 LTS 版本?
UnsupportedClassVersionError的根因是什么?
7. 下一篇
S04-B02 · 基本类型与装箱:== 什么时候会骗你
下一篇用实测解释"127 相等、128 不相等"的原因——以及 Integer.valueOf 的缓存在哪里。
本篇属于系列《S04 · Java 语言核心》。旧文来源:A-1836/A-1893/A-1640(A-1831 → S01 duplicate_ref);扩展点 E-S04-001/002。字节码输出为 OpenJDK 21 实测。