javac编译是源码到字节码的结构化转换,非翻译:含词法分析(拆token)、语法分析(建AST)、语义分析(查类型、填符号表)、字节码生成(输出iconst_1等指令),仅保证字节码合法,不涉及平台优化或运行时行为。

javac 编译阶段:源码变字节码不是“翻译”,而是结构化转换
Java 源代码(.java)不能被 CPU 直接执行,javac 的作用不是生成“等效机器指令”,而是按 JVM 规范产出严格格式的二进制字节码(.class)。这个过程包含四个不可跳过的内部阶段:
- 词法分析:把
int x = 1 + y;拆成int、x、=、1、+、y等 token - 语法分析:构建抽象语法树(AST),确认
+是二元运算符、y在作用域内等 - 语义分析:检查类型兼容性(比如
String不能赋给int)、填充符号表(记录每个类/方法/变量的签名) - 字节码生成:基于 AST 和符号表,输出符合 JVM 指令集的二进制流,例如
iconst_1、istore_0、getstatic
关键点:javac 不做任何平台相关优化(如 CPU 寄存器分配),也不处理运行时行为(如多线程、GC)。它只保证字节码合法——魔数是 0xCAFEBABE、常量池格式正确、方法签名可解析。一个 public class A 必须放在 A.java 中,否则 javac 直接报错 error: class A is public, should be declared in a file named A.java。
JVM 加载与链接:字节码进内存 ≠ 就能执行
运行 java A 时,JVM 并不直接执行 A.class,而是先走加载 → 验证 → 准备 → 解析四步链接流程:
- 加载:由
Application ClassLoader读取文件字节流,生成Class对象,存入元空间(JDK 8+) - 验证:检查魔数、版本号(如 JDK 21 编译的 class 用 JDK 17 运行会报
Unsupported major.minor version)、常量池引用是否越界 - 准备:为
static int x = 5;分配内存并设初值0(不是5),此时x还未被赋值 - 解析:把常量池里的符号引用(如
"java/lang/System")替换成内存中真实地址,失败则抛NoClassDefFoundError
注意:这阶段不执行任何 Java 代码(static{} 块、static 变量赋值都还没动)。只有当首次主动使用该类(比如调用 main 方法)时,才触发初始化。
立即学习“Java免费学习笔记(深入)”;
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
JIT 编译:热点代码才转机器码,且只在运行时发生
字节码默认由解释器逐条执行,但 HotSpot JVM 会在运行时识别“热点”——比如某个方法被调用超过 -XX:CompileThreshold=10000 次(Client VM 默认值),或某段循环执行超阈值。这时 JIT 编译器(C1 或 C2)介入:
- C1 编译快、优化浅(如方法内联、空检查消除),适合启动阶段
- C2 编译慢、优化深(如逃逸分析、循环展开),适合长期运行的服务器应用
- 编译后生成的机器码缓存在代码缓存区(
-XX:ReservedCodeCacheSize可调),下次调用直接跳转执行 - 若运行时假设失效(如某对象实际逃逸了),JVM 会逆优化(deoptimization),退回解释执行
重要提醒:JIT 不编译整个类,只编译具体方法;不保证所有代码都被编译(冷代码永远解释执行);编译结果与当前 CPU 架构强绑定(x86 生成的机器码不能在 ARM 上跑)。
为什么没有“一次编译,到处运行”的机器码?
因为机器码天生平台相关。JVM 的设计本质是用一层字节码抽象屏蔽硬件差异:javac 输出的 .class 文件能在任何安装了对应版本 JVM 的设备上加载,但最终执行的机器码,一定是由该设备本地的 JIT 编译器动态生成的。
这意味着你无法把一个 Linux 上用 JDK 21 运行生成的 JIT 代码,复制到 Windows 上复用——那堆机器码对 Windows 内核和 CPU 来说就是乱码。真正跨平台的是字节码,不是机器码;真正“到处运行”的是 JVM,不是 Java 程序本身。

















