分层编译默认启用,jvm依方法热度动态升级:0层解释执行,1层c1基础优化,2/3层c1+profiling,4层c2激进优化;触发依赖调用与回边计数,非固定阈值。

Java 的分层编译(Tiered Compilation)默认启用,JVM 会根据方法执行热度,逐步将解释执行的方法升级为不同优化等级的编译版本(从 C1 快速编译到 C2 深度优化)。要确认某个方法究竟被编译到了哪一层,最直接、无需额外工具的方式就是开启 -XX:+PrintCompilation 并解析其输出日志。
日志字段含义与层级识别关键
该参数输出每行类似:
132 1 3 java.lang.String::hashCode (67 bytes)其中第 2 列(示例中是 1)即为编译层级(tier),对应关系如下:
- 1:纯解释执行(Interpreter),不属 JIT 编译,但日志中仍可能标记为 tier 1(注意:这是历史兼容标识,并非真正编译)
- 2:C1 编译(Client Compiler),基础优化,低延迟,适用于短期热点
- 3:C2 编译(Server Compiler),高级优化(如循环展开、向量化、内联深度更大),适用于长期稳定热点
- 4:C2 编译 + 被标记为“已优化”(optimized),通常出现在方法被多次重编译后,或触发去优化(deoptimization)再回归时
补充说明:若某方法在日志中反复出现 tier=2 后又出现 tier=3,说明它经历了从 C1 升级到 C2 的过程;若只出现一次且为 tier=3,说明 JVM 预判其为高价值热点,跳过 C1 直接 C2(如启动时通过 -XX:CompileThreshold=1 强制触发,或使用 -XX:+UseG1GC 等组合策略影响判定)。
如何过滤并定位目标方法
日志量大,需结合工具快速聚焦。推荐以下组合:
- 启动时加
-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation,后者生成详细 XML 日志(hotspot-pid-*.xml),可用JITWatch可视化分析调用链与编译路径 - 命令行实时过滤:
java -XX:+PrintCompilation MyApp 2>&1 | grep "methodName" - 关注“made not entrant”或“made zombie”字样:表示旧版本编译代码因去优化而失效,新 tier 已就绪
影响层级决策的常见因素
JVM 不是仅看调用次数,而是综合运行时 profiling 数据动态判断。关键信号包括:
- 方法调用计数(Invocation Counter)达阈值(默认 10000),触发 C1 编译
- 循环回边(Back-edge)执行次数超限(默认 14000),更倾向触发 C2(尤其含大循环的方法)
- 方法内联深度受限于
-XX:MaxInlineLevel和-XX:FreqInlineSize,若内联失败,可能抑制升级至 C2 - 存在异常处理块、synchronized 块、或大量分支未收敛,可能延迟进入 C2 或降级为 C1
验证是否真正生效的小技巧
仅看 PrintCompilation 不足以确认最终执行的是哪个版本。可配合:
-
-XX:+PrintInlining:查看哪些调用被内联,内联深度反映 C2 参与程度 -
-XX:+PrintAssembly(需 hsdis):比对不同 tier 下生成的汇编指令差异(如 C2 更多使用 SIMD 指令) - 用 JMH 写微基准测试,固定 warmup 迭代数后观察吞吐量跃升点,常与 tier=3 首次出现时间吻合










