JIT 即时编译
面试提问
"JVM 的 JIT 编译器是什么?它如何提升 Java 程序性能?"
核心概念
为什么需要 JIT?
Java 程序的执行过程:
源代码(.java) → 字节码(.class) → 解释执行 / JIT 编译 → 机器码执行问题:解释执行效率低,每次都要翻译字节码
解决:JIT(Just-In-Time)即时编译器,将热点代码编译成机器码缓存,后续直接执行
JIT 的工作原理
┌─────────────────────────────────────────────────┐
│ JVM 执行 │
├─────────────────────────────────────────────────┤
│ 方法调用 │
│ ↓ │
│ 解释器执行 + 方法调用计数器 │
│ ↓ │
│ 热点代码?(计数器超过阈值) │
│ ↓ 是 │
│ JIT 编译 → 生成机器码 → 缓存到 CodeCache │
│ ↓ │
│ 后续调用直接执行机器码 │
└─────────────────────────────────────────────────┘HotSpot 的两种 JIT 编译器
1. C1 编译器(Client 编译器)
特点:
- 编译速度快
- 优化程度低
- 适合启动时间敏感的应用
优化内容:
- 局部优化(死代码消除、常量折叠)
- 简单内联
2. C2 编译器(Server 编译器)
特点:
- 编译速度慢
- 优化程度高
- 适合长时间运行的服务端应用
优化内容:
- 全局优化
- 逃逸分析
- 循环优化
- 积极内联
分层编译(Tiered Compilation)
JDK8 默认开启,结合 C1 和 C2 的优势:
第 0 层:解释执行
第 1 层:C1 编译(简单优化)
第 2 层:C1 编译(更多优化)
第 3 层:C1 编译(完整 profiling)
第 4 层:C2 编译(最高优化)# 开启分层编译(JDK8 默认开启)
-XX:+TieredCompilation
# 指定编译器
-client # 使用 C1
-server # 使用 C2热点探测
方法调用计数器
统计方法被调用的次数,超过阈值触发 JIT 编译。
# 方法调用阈值(Client 模式默认 1500,Server 模式默认 10000)
-XX:CompileThreshold=10000
# 分层编译时的阈值调整因子
-XX:Tier0CompileThreshold=1000
-XX:Tier1CompileThreshold=2000
-XX:Tier2CompileThreshold=3000
-XX:Tier3CompileThreshold=5000
-XX:Tier4CompileThreshold=10000回边计数器
统计循环回边的次数,用于识别循环热点。
当循环执行次数超过阈值,触发栈上替换(OSR),在循环过程中替换为编译后的机器码。
JIT 核心优化技术
1. 方法内联(Inlining)
将被调用方法的代码直接嵌入调用处,消除方法调用开销。
优化前:
public int add(int a, int b) {
return a + b;
}
public int calculate(int x, int y) {
return add(x, y) * 2;
}优化后(内联):
public int calculate(int x, int y) {
return (x + y) * 2; // add 方法被内联
}参数控制:
-XX:MaxInlineSize=35 # 可内联方法的最大字节码大小
-XX:FreqInlineSize=325 # 频繁调用方法的最大字节码大小2. 逃逸分析(Escape Analysis)
分析对象是否"逃逸"出方法或线程,进行针对性优化。
三种优化:
| 优化 | 条件 | 效果 |
|---|---|---|
| 栈上分配 | 对象不逃逸出方法 | 对象在栈上分配,无需 GC |
| 标量替换 | 对象可分解 | 用局部变量替代对象字段 |
| 锁消除 | 对象不逃逸出线程 | 消除同步锁 |
示例 - 标量替换:
// 原代码
public void process() {
Point p = new Point(1, 2); // 对象不逃逸
System.out.println(p.x + p.y);
}
// 优化后(标量替换)
public void process() {
int x = 1; // 直接用局部变量
int y = 2;
System.out.println(x + y);
}# 开启逃逸分析(默认开启)
-XX:+DoEscapeAnalysis
# 开启标量替换(默认开启)
-XX:+EliminateAllocations3. 锁优化
锁消除:逃逸分析发现对象不逃逸出线程,消除同步锁
public String concat(String s1, String s2) {
StringBuffer sb = new StringBuffer(); // 局部对象,不逃逸
sb.append(s1);
sb.append(s2);
return sb.toString();
}
// StringBuffer 的同步锁会被消除锁膨胀:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
4. 循环优化
| 优化类型 | 说明 |
|---|---|
| 循环展开 | 减少循环次数,增加每次迭代的工作量 |
| 循环不变量外提 | 将循环内不变的代码移到循环外 |
| 循环融合 | 合并多个相邻的循环 |
// 优化前
for (int i = 0; i < n; i++) {
a[i] = i * 2;
}
// 循环展开(展开因子为 2)
for (int i = 0; i < n; i += 2) {
a[i] = i * 2;
a[i + 1] = (i + 1) * 2;
}5. 死代码消除
删除永远不会执行的代码或计算结果未使用的代码。
// 优化前
public int calculate(int x) {
int a = 10; // 未使用
int b = x * 2; // 未使用
int c = x + 1; // 实际返回值
return c;
}
// 优化后
public int calculate(int x) {
return x + 1;
}CodeCache
JIT 编译后的机器码存储在 CodeCache 中。
# CodeCache 大小设置
-XX:InitialCodeCacheSize=160K # 初始大小
-XX:ReservedCodeCacheSize=240M # 最大大小(JDK8 默认 240M)
# CodeCache 满了会怎样?
# - JIT 停止编译新代码
# - 程序性能下降
# - 可能出现警告:CodeCache is full. Compiler has been disabled.CodeCache 监控:
# 打印 CodeCache 使用情况
-XX:+PrintCodeCache
# JDK9+ 使用分段 CodeCache
-XX:+SegmentedCodeCacheJIT 相关参数速查
编译控制
| 参数 | 默认值 | 说明 |
|---|---|---|
-XX:+TieredCompilation | 开启 | 分层编译 |
-XX:CompileThreshold | 10000 | 触发编译的调用次数 |
-XX:+PrintCompilation | 关闭 | 打印编译信息 |
内联控制
| 参数 | 默认值 | 说明 |
|---|---|---|
-XX:MaxInlineSize | 35 | 内联方法最大字节码大小 |
-XX:FreqInlineSize | 325 | 频繁调用方法的内联限制 |
逃逸分析
| 参数 | 默认值 | 说明 |
|---|---|---|
-XX:+DoEscapeAnalysis | 开启 | 逃逸分析 |
-XX:+EliminateAllocations | 开启 | 标量替换 |
-XX:+EliminateLocks | 开启 | 锁消除 |
JIT vs AOT
| 对比项 | JIT | AOT |
|---|---|---|
| 编译时机 | 运行时 | 编译时 |
| 启动速度 | 慢(需要预热) | 快 |
| 峰值性能 | 高(运行时优化) | 中等 |
| 动态特性 | 支持 | 有限支持 |
| 典型工具 | HotSpot JIT | GraalVM Native Image |
GraalVM Native Image:将 Java 应用编译成原生可执行文件,启动时间毫秒级。
面试要点总结
| 问题 | 答案要点 |
|---|---|
| JIT 是什么? | 即时编译器,将热点代码编译成机器码缓存,提升执行效率 |
| C1 和 C2 的区别? | C1 编译快优化少,C2 编译慢优化多;分层编译结合两者 |
| 热点探测方式? | 方法调用计数器、回边计数器 |
| JIT 主要优化? | 方法内联、逃逸分析(栈上分配/标量替换/锁消除)、循环优化 |
| 逃逸分析的作用? | 判断对象是否逃逸,进行栈上分配、标量替换、锁消除优化 |
相关题目
- 为什么 Java 说"解释执行",但性能可以接近 C++?
- 什么是方法内联?有什么好处?
- 逃逸分析能做哪些优化?
- 分层编译的层次划分是什么?
参考资料
- JIT Compilation in HotSpot (opens in a new tab)
- 《深入理解 Java 虚拟机》- 周志明
- GraalVM Native Image (opens in a new tab)