OSR是C2编译器自动触发的机制,需满足循环已运行一段时间且被判定为热点;常见失效原因包括循环体过简、含未内联虚方法、禁用C2或回边计数未达140%阈值。

OSR 不是你要主动“开启”的功能,而是 C2 编译器在满足条件时自动触发的底层机制;你真正能做的,是让循环结构和运行环境符合 OSR 的触发前提。
为什么你的长循环没被 OSR 优化?
OSR 只对“已运行一段时间 + 被判定为热点”的循环体生效,不是所有长循环都会触发。常见失效原因包括:
- 循环体太短或逻辑太简单(例如
i++空循环),JVM 认为不值得编译 - 循环内含大量未内联的虚方法调用(如
list.get(i)),阻碍热点识别与编译稳定性 - 使用了
-XX:-TieredStopAtLevel1或强制禁用 C2(如-client参数,JDK 8+ 已无效但配置残留仍可能干扰) - 循环执行次数未达
OnStackReplacePercentage阈值(默认为 140%,基于回边计数器)
如何构造一个容易触发 OSR 的循环
目标是让 JVM 快速识别“这个循环正在长期运行”,并稳定收集 profiling 数据。关键点:
- 确保循环体有足够计算量(如浮点运算、数组访问、对象字段读写),避免被 JIT 提前判定为“无副作用”而跳过
- 避免在循环内修改影响逃逸分析的变量(如把局部对象赋给全局引用),否则 C2 可能放弃激进优化
- 用
while (true)或高迭代次数的for循环,比带复杂条件判断的循环更容易累积回边计数 - 不要在循环中频繁抛异常或调用
System.out.println()—— 这些会污染 profiling,导致编译器降级或放弃 OSR
示例中易触发 OSR 的写法:
data
-
-XX:+PrintCompilation:观察输出中是否出现osr标记,例如123 45 b java.lang.String::hashCode (67 bytes) osr -
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly(需 hsdis):查看编译后代码是否包含循环展开、向量化等 C2 特征 -
-XX:CompileCommand=print,*YourClass.yourMethod:精准定位某方法的编译日志
注意:osr 日志只出现在 C2 编译队列中,C1 不支持 OSR;若看到大量 compiled 但无 osr,说明循环未满足 OSR 条件,或已被提前编译为完整方法版本(即非 OSR 场景)。
OSR 的真实代价和限制
它不是银弹。每次 OSR 都要保存解释栈帧、重建寄存器状态、跳转到新入口,这个过程本身有开销:
- 仅在循环已执行数百次后才发生,前期全是解释执行 —— 所以“预热时间”不可忽略
- OSR 编译后的代码无法再做某些深度优化(如完全的循环不变量外提),因为栈状态必须兼容解释器快照
- 如果循环内有未处理的
InterruptedException或synchronized块,OSR 可能被延迟甚至跳过 - 多线程竞争同一循环变量时,JVM 可能因同步开销放弃 OSR 判定
最常被忽略的一点:OSR 只替换当前线程的栈上循环,其他线程仍在解释执行 —— 它不是全局替换,也不是原子切换。










