HotSpot 通过方法调用计数器和回边计数器精确识别热点代码:前者统计方法调用次数,后者统计循环回跳次数;任一达到阈值即触发JIT编译,OSR编译仅依赖回边计数器。

HotSpot 用两个计数器判断热点代码
OpenJDK(确切说是 HotSpot VM)不靠采样,而是通过精确计数来识别热点代码。它维护两个独立的计数器:方法调用计数器和回边计数器。前者统计方法被调用的次数,后者统计循环体(如 for、while)执行“回跳”到循环头的次数——也就是循环迭代次数。只有当任一计数器达到阈值,对应的方法才会被标记为热点,触发 JIT 编译。
-XX:CompileThreshold 参数影响实际触发点
默认阈值不是固定不变的,它取决于 JVM 启动模式:
- Server 模式(64 位常见,默认):方法调用计数器阈值是
10000,回边计数器也是10000 - Client 模式(已基本弃用):两者默认都是
1500 - 可通过 JVM 参数显式调整,例如:
-XX:CompileThreshold=5000会同时降低两个计数器的触发门槛 - 注意:
-XX:+TieredStopAtLevel=1等分层编译控制参数会影响计数器是否启用或如何累积,不能只看单个参数
方法未内联时,计数器行为容易被误判
如果一个高频调用的方法被 JIT 决定内联(inline),它的字节码就不再以独立方法形式存在,方法调用计数器也就不会为其单独累加——此时“热点”会转移到调用方方法上。这意味着:
- 你看到的
javap -c输出里没被内联的方法,才可能有独立的调用计数 - 用
-XX:+PrintCompilation观察日志时,若某方法长期不出现,未必不热,可能是已被内联进父方法了 -
-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining可验证内联决策,比单纯盯计数器更可靠
OSR 编译只依赖回边计数器,且发生在方法执行中
当一个长循环在解释执行中反复跳转(比如一个跑了几万次的 while(true)),即使该方法本身调用次数很少,只要回边计数器达标,JIT 就会启动 OSR(On-Stack Replacement)编译——即在栈上把正在运行的解释版本替换成编译后的本地代码。这说明:
- OSR 不需要方法“完整执行完一次”,它针对的是正在运行中的循环体
- 这类热点无法通过简单统计“方法调用频次”发现,必须关注循环内部的回边行为
- 高 CPU 占用但方法调用不多的场景(如事件循环、游戏主循环),往往就是 OSR 的典型用武之地
-XX:CompileThreshold 调数字,先用 -XX:+PrintCompilation 和 -XX:+PrintInlining 看清实际发生了什么。


















