服务刚发布时延迟很高,运行十分钟后恢复正常,可能和 JIT 有什么关系?怎么验证?
先说结论:JVM 先以解释或较低优化层级快速启动,通过调用计数和回边计数识别热点,再把热点方法编译成本地代码。JIT 可根据运行时类型信息做内联、常量折叠、锁消除、标量替换和去虚拟化,并在假设失效时去优化。 分层编译在启动速度与峰值性能间折中,具体编译器和阈值由 JVM 实现与配置决定。
我先给结论,再说明它在项目里解决什么问题。理解解释执行、分层编译、热点探测、内联和逃逸分析。JVM 先以解释或较低优化层级快速启动,通过调用计数和回边计数识别热点,再把热点方法编译成本地代码。JIT 可根据运行时类型信息做内联、常量折叠、锁消除、标量替换和去虚拟化,并在假设失效时去优化。 分层编译在启动速度与峰值性能间折中,具体编译器和阈值由 JVM 实现与配置决定。
核心机制我会按一次真实执行过程来讲。沿着「解释执行并采集 Profiling → 热点方法达到阈值 → 生成优化机器码 → 运行守卫与内联代码 → 假设失效后去优化」观察输入、状态与输出,这些阶段都可以从日志、指标或源码里验证。 解释执行并采集 Profiling 方法初期由解释器执行,同时记录调用次数、分支概率和接收者类型等运行画像。 热点方法达到阈值 热点计数触发分层编译,C1 快速编译并采样,C2 使用更多画像做激进优化。
实现细节只抓关键入口,不会整段背源码。-XX:+PrintCompilation:编译层级与去优化。 JFR Compilation/Deoptimization:热点方法时间线。 我会先确认请求实际走到了哪条路径,再用运行数据验证,不会只看类名或配置猜测。
放到生产使用时,我会关注参数和验证数据。JMH 使用多 fork 和充分 warmup,对单态/多态调用比较;防止死代码消除并记录 Code Cache。 固定输入和基线 先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「warmup 后吞吐」为主基线,记录值应满足「进入稳定平台」;同时保存 编译队列长度、Code Cache 使用,使后续变化能够回到同一时间轴比较。
最后补充常见误区和使用边界。JIT 日志显示某核心方法因新实现类加载从单态变多态,已编译代码反复去优化和重编译。减少热路径动态类型、检查代码缓存并稳定加载时机后,毛刺消失。 用一次微基准测试解释器阶段下结论:编译队列长度:性能测试加 Blackhole 与多 fork。 忽略逃逸导致基准被完全消除:Code Cache 使用:稳定热路径类型。 方案:更适合的场景:主要收益:代价与边界。 默认分层编译:通用服务:启动与峰值性能平衡:画像变化仍可能去优化。 AOT/CDS 辅助:冷启动敏感:减少类加载或初始编译成本:峰值优化通常仍需 JIT。 禁用/限制高级编译:诊断或极特殊稳定场景:行为更可预测:长期吞吐显著下降。 选型至少带上 堆与非堆容量、对象分配速率、存活率和停顿目标,并用上面的量化基线验证;未知数据应明确为待测假设。