为何示例中的Java方法会被编译为汇编代码?
为什么你的方法仍然被编译?几个容易忽略的关键点
首先先确认你的测试代码和环境信息:
测试代码
public class Inline { public static void main(String[] args) throws Exception { long upto = Long.parseLong(args[0]); for(int i = 0; i < upto; i++) { int x = inline1(); Thread.sleep(1); } } public static int inline1() { return inline2(); } public static int inline2() { return inline3(); } public static int inline3() { return 4; } }
Java环境
java version "1.8.0_144" Java(TM) SE Runtime Environment (build 1.8.0_144-b01) Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
你之前误以为Thread.sleep()触发的安全点会让计数器衰减到无法达到编译阈值,但实际所有方法都被编译了,主要忽略了以下几个核心关键点:
1. 安全点≠计数器强制衰减
安全点是JVM让线程暂停的执行节点,但不是每次安全点都会触发调用/回边计数器的衰减。计数器衰减是JVM的“热度冷却”机制,目的是避免把偶尔高调用的方法误判为热点,它的触发有严格条件:
- 衰减操作通常只在JVM执行全局操作(比如垃圾回收)时才会在安全点执行,单纯的
Thread.sleep()只会让当前线程进入等待状态,不会触发全局安全点的计数器衰减逻辑。 - 即使触发衰减,也不是直接清零或大幅递减,而是将计数器值乘以默认0.5的衰减系数,且只有当计数器超过
CompileThreshold的一半时才会触发。你的循环调用次数足够多,计数器仍然能累积到阈值。
2. Server VM默认编译阈值与循环次数刚好匹配
你使用的64位Server VM,默认CompileThreshold(触发C2优化编译的阈值)是10000,而你的循环次数正好是10000次。inline1()、inline2()、inline3()的调用计数器都会被累加10000次,刚好踩中触发编译的阈值线。
如果循环次数低于10000,可能会看到部分方法不被编译,但10000次刚好满足要求。
3. 分层编译默认开启,低阈值C1编译会提前触发
Java 8的Server VM默认开启分层编译(Tiered Compilation),它包含多个编译层级:
- 低层级(如C1快速编译)的触发阈值远低于C2,默认1500次调用就会触发C1编译。
- 方法被C1编译后,后续调用会执行编译后的代码,但计数器仍会继续累积,直到达到C2阈值再进行优化编译。
所以在你的测试中,可能循环执行到1500次左右时,这些方法就已经被C1编译了,后续循环只是在执行编译后的代码,这也解释了为什么最终所有方法都出现在PrintCompilation的输出中。
4. 短调用链的内联特性加速编译判断
你的三个方法都是简单静态方法,调用链极短(inline1()→inline2()→inline3())。JVM在热点判断时会对这种短链调用特殊处理:
- 即使单个方法的调用计数器还没完全达标,JVM也可能因为整个调用链的热度提前将这些方法纳入编译计划,尤其是方法体足够简单时,内联优化成本极低,JVM会更倾向于提前编译。
内容的提问来源于stack exchange,提问作者An SO User
相关产品推荐
相关产品推荐

