Java 的编译过程分为 两个主要阶段:
- 前端编译(源代码 → 字节码)
- 运行时编译(字节码 → 机器码)
下面通过 流程图、示例代码和底层原理 详细解析整个过程。
一、前端编译:javac
将 .java
文件编译为 .class
字节码
流程图解:
graph LR
A[.java 源代码] --> B[词法分析]
B --> C[语法分析]
C --> D[语义分析]
D --> E[生成字节码]
E --> F[.class 文件]
分步骤解析:
-
词法分析
-
将源代码拆分为 Token(关键字、标识符、运算符等)
-
示例:
javaint a = 1 + 2;
→ 拆分为:
int
,a
,=
,1
,+
,2
,;
-
-
语法分析
- 根据 Token 生成 抽象语法树(AST)
- 检查语法错误(如缺少分号、括号不匹配)
-
语义分析
-
检查 类型匹配 、变量是否声明 等语义问题
-
示例:
javaString s = 123; // 编译报错:类型不兼容
-
-
生成字节码
-
使用
javac
生成符合 JVM 规范的.class
文件 -
通过
javap -v
反编译查看字节码:bashjavap -c Demo.class
-
二、运行时编译:JVM 将字节码转换为机器码
JVM 执行 .class
文件时,会通过 解释执行 和 即时编译(JIT) 两种方式优化性能。
1. 解释执行(Interpreter)
- 逐行解释字节码,直接执行
- 优点:启动快
- 缺点:执行效率低(每次运行都要重新解释)
2. 即时编译(JIT Compiler)
- 将热点代码(HotSpot)编译为机器码,缓存复用
- 触发条件:方法/代码块被多次调用(默认阈值:
-XX:CompileThreshold=10000
) - 优化级别:
- C1 编译器(Client 模式):快速编译,优化较少
- C2 编译器(Server 模式):深度优化,适合长期运行的服务
JIT 优化示例:
java
// 原始代码
for (int i = 0; i < 1000; i++) {
sum += i;
}
// JIT 可能优化为:
sum += 499500; // 直接计算结果
三、类加载机制(连接阶段)
.class
文件加载到 JVM 时,会经历以下步骤:
graph LR
A[加载] --> B[验证]
B --> C[准备]
C --> D[解析]
D --> E[初始化]
-
加载(Loading)
- 通过
ClassLoader
查找.class
文件并载入内存 - 生成
Class<?>
对象
- 通过
-
验证(Verification)
- 检查字节码是否符合 JVM 规范(防止恶意代码)
-
准备(Preparation)
- 为 静态变量 分配内存并赋默认值(如
int
默认为0
)
- 为 静态变量 分配内存并赋默认值(如
-
解析(Resolution)
- 将符号引用(如
java.lang.Object
)转换为直接引用
- 将符号引用(如
-
初始化(Initialization)
- 执行静态代码块(
static {}
)和静态变量赋值
- 执行静态代码块(
四、实战:从源码到执行的全流程
示例代码:
java
// Demo.java
public class Demo {
public static void main(String[] args) {
int result = add(1, 2);
System.out.println(result);
}
static int add(int a, int b) {
return a + b;
}
}
全流程:
-
编译
bashjavac Demo.java # 生成 Demo.class
-
运行
bashjava Demo # 输出 3
-
JVM 内部执行
- 解释执行
main()
方法 - JIT 发现
add()
被频繁调用,将其编译为机器码优化
- 解释执行
五、关键面试题
-
javac
和java
命令的区别?javac
:前端编译器,生成字节码java
:启动 JVM,解释执行字节码 + JIT 优化
-
什么是字节码?为什么 Java 是"跨平台"语言?
- 字节码是 JVM 的中间代码,由
.class
文件存储 - 跨平台依赖 JVM:不同系统有对应的 JVM 实现,统一执行字节码
- 字节码是 JVM 的中间代码,由
-
JIT 和 AOT(Ahead-of-Time)编译的区别?
- JIT:运行时动态编译(如 HotSpot)
- AOT:提前编译为机器码(如 GraalVM Native Image)
掌握 Java 编译过程后,你可以:
✅ 理解 javac
报错的根本原因
✅ 优化 JVM 参数(如调整 JIT 阈值 -XX:CompileThreshold
)
✅ 分析类加载冲突问题(如 NoClassDefFoundError
)