第一次接触Java性能优化时,我也曾被"JIT编译"和"Java编译器编译"这两个术语搞得晕头转向。直到在线上系统遇到性能瓶颈,通过JVM调优实战才真正理解它们的差异。简单来说:
这就像把一本英文小说翻译成世界语(javac),再由各地读者根据需要翻译成母语(JIT)。前者保证通用性,后者追求执行效率。
使用javac编译时:
bash复制javac Main.java → Main.class
经历以下关键阶段:
关键特点:一次编译到处运行,但牺牲了特定平台的优化机会
当HotSpot VM发现某方法调用次数超过阈值(默认10000次):
实测对比:
| 执行方式 | 测试案例(斐波那契数列) | 耗时(ms) |
|---|---|---|
| 纯解释模式 | fib(30) | 450 |
| JIT编译后 | fib(30) | 120 |
现代JVM采用混合编译模式:
C1编译器(客户端编译器)
C2编译器(服务端编译器)
典型工作流程:
通过-XX:+PrintCompilation可观察:
log复制156 % 3 com.Test::main @ 2 (58 bytes)
其中%表示栈上替换(OSR),即在方法执行过程中替换代码。
bash复制-XX:+TieredCompilation # 启用分层编译(默认)
-XX:CompileThreshold=10000 # 调整触发阈值
-XX:+PrintCompilation # 打印编译日志
预热不足:性能测试前需确保热点方法已编译
java复制// 正确的预热方式
for(int i=0; i<20000; i++) {
hotMethod();
}
代码缓存溢出:
bash复制-XX:ReservedCodeCacheSize=256m # 调大代码缓存
反优化陷阱:
GraalVM带来的新可能:
但生产环境仍需注意:
在实际金融系统调优中,我们通过JITWatch工具分析发现:某个高频调用的DTO转换方法因未内联导致30%性能损耗。通过调整方法长度(-XX:MaxInlineSize=35)最终获得19%的吞吐提升。这种细粒度优化正是理解JIT机制的价值所在。