JVM热点代码预热可避免上线后编译风暴,需提前识别核心方法、用分层编译+轻量调用触发C1编译、预留充足Code Cache并验证缓存生效。

系统刚启动时,热点代码还没被 JIT 编译,所有方法都靠解释器执行,性能偏低;等流量进来、计数器触发编译,又容易因瞬时压力导致 CPU 飙升、响应延迟。Code Cache 本身不主动“预热”,但可以通过控制 JIT 编译节奏,让热点代码在低负载阶段就完成编译并缓存到 Code Cache 中,从而避开上线后高并发下的编译风暴。
提前识别并标记核心热点方法
不是所有方法都需要预热,重点是业务主链路中调用频次高、逻辑稳定、无强外部依赖的方法(比如订单创建主流程、用户鉴权 check 方法)。可通过以下方式定位:
- 用 -XX:+PrintCompilation 启动应用,观察预发或压测阶段哪些方法最快进入 C1/C2 编译队列
- 结合 APM 工具(如 SkyWalking、Arthas)统计方法调用耗时与 QPS,筛选出高频+高耗时组合
- 避免预热日志打印、异步上报、数据库写入等易受环境干扰的非纯计算型方法
用分层编译 + 启动期轻量调用触发编译
JVM 默认启用分层编译(Tiered Compilation),Level 1(C1)编译快、开销小,适合启动期预热。关键是在应用 Ready 后、流量接入前,主动触发几次核心方法调用:
- 在 Spring Boot 的 ApplicationRunner 或自定义初始化 Bean 中,对目标方法做 3–5 次空参/模拟参数调用(无需真实业务结果)
- 确保调用路径不被 JIT 逃逸分析排除(例如避免只在 if 分支里调用,应保证稳定执行路径)
- 配合 -XX:CompileThreshold=1000(默认 10000)适当降低阈值,加快 C1 编译触发,但不宜过低以免编译过多冷代码
预留充足且稳定的 Code Cache 空间
Code Cache 是堆外内存区域,大小固定,默认约 240MB(JDK8)或 24MB(JDK11+),若空间不足,JIT 会停编译或退回到解释执行,预热失效:
- 通过 -XX:ReservedCodeCacheSize=512m -XX:InitialCodeCacheSize=256m 显式设置,留出冗余(建议 ≥300MB)
- 用 jstat -compiler
观察 Total(总编译数)和 Invalid(失效编译数),若后者持续增长,说明 Code Cache 不足或存在频繁 recompile - 避免在预热阶段混入大量动态代理类(如 Spring AOP 过度切面),它们会快速占满 Code Cache
验证预热是否生效
不能只看日志有没有 “Compiled” 字样,要确认编译结果真正进缓存并被复用:
- 启动后立即执行 jcmd
VM.native_memory summary scale=MB ,检查 Code 区域已使用量是否明显上升(比如从 20MB → 80MB+) - 用 jstack
查看是否有 CompilerThread 正在活跃工作;预热完成后该线程应趋于空闲 - 接入真实请求后,对比预热/未预热两版的 P99 延迟曲线:预热成功时,首波流量不应出现明显毛刺











