解释执行(Interpreter)
JVM 启动后,默认使用解释器(Interpreter)逐条解释字节码。优点是启动快、内存消耗低,但执行效率低,因为每次方法调用都要逐条解析。
即时编译(JIT Compilation)
为了提高热点代码的执行效率,JVM 引入了 即时编译器(JIT),将在运行时把热点方法编译为机器码,提高执行速度,代价是编译本身会消耗 CPU 和内存。
HotSpot 的执行模型
HotSpot JVM 实际上是一个混合模式执行引擎,具备:
JVM 会将调用频率高的代码标记为热点,通过统计字节码执行计数器(Invocation Counter)进行判断:
热点类型 | 描述 |
---|---|
方法调用热点 | 某个方法被频繁调用 |
循环热点 | 循环体中某段代码被频繁执行 |
默认阈值(client 模式):
方法调用达到 1500 次(可通过 -XX:CompileThreshold 修改)即可触发编译
✅ C1(Client 编译器)
编译速度快,适合前期运行
支持基本优化(如内联、常量折叠)
会插入 profiling 信息(profile-guided)
✅ C2(Server 编译器)
编译耗时较长,但生成机器码更优
使用 SSA、全局优化、逃逸分析等高级技术
适合长时间运行的服务类应用
默认行为:前期由解释器 + C1 执行,收集 profile 后交由 C2 编译。
JIT 编译器不仅将字节码转为机器码,还执行了多项优化:
案例:
public int sum(int a, int b) {
return a + b; // 会被内联
}
循环展开(Loop Unrolling)
减少循环体中判断次数,提高流水线效率。
去虚拟化(Devirtualization)
将虚方法调用转为直接调用(static call),减少查表过程。
逃逸分析 + 栈上分配(Escape Analysis)
将不会逃出方法作用域的对象分配到栈上,减少 GC 压力。
案例解释:
public String createName() {
Person p = new Person("Tom"); // 不逃逸,可在栈上分配
return p.getName();
}
开启 JIT 日志
使用以下参数查看 JIT 编译行为:
-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+LogCompilation
可配合 JITWatch 等工具图形化分析。
热点方法识别 & 编译状态
jcmd Compiler.codecache
查看 CodeCache 使用率,避免编译失败或被回退(deoptimization)。
生产案例:某业务突发 Full GC 原因竟然是 JIT 编译失败
一次业务峰值时,C2 编译因 CodeCache 空间不足而频繁回退至解释器,导致 GC 压力暴增。排查发现 -XX:ReservedCodeCacheSize=240M 太小,扩大为 512M 后问题缓解。
AOT 编译(Ahead-Of-Time)
Java 9 引入 jaotc 工具,提前编译字节码为本地代码,降低启动开销。
Graal 编译器
作为 JDK 项目的未来方向,Graal 以 JVM 的 plugin 形式运行,具备更强优化能力,支持 Java + 原生混合编译。
在 GraalVM 下甚至可以让 Java 执行效率媲美 C++
✅ 总结:JIT 是 JVM 性能的顶梁柱
特性 | 说明 |
---|---|
Hotspot 探测 | 自动识别热点方法 |
分层编译 | 启动快 + 性能高的组合拳 |
高级优化 | 内联 / 去虚拟化 / 逃逸分析 |
可诊断性 | JITWatch / 编译日志辅助分析 |
在性能敏感系统中,JIT 是不可忽视的关键角色。了解它、调优它,你将拥有驾驭 Java 性能的核心能力。
关注 + 点赞 + 收藏,持续更新高质量 JVM 专栏!
下一篇《第 10 篇:JVM 与容器化部署调优实践》,将深入剖析容器环境中的 JVM 陷阱与优化策略,Docker/K8s + Java 性能调优实战等你来读!