什么是预热?它有什么作用?
一则或许对你有用的小广告
欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书
《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于
Spring AI + Spring Boot 3.x + JDK 21...,查看介绍《从零手撸:仿小红书(微服务架构)》 已完结,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...,查看介绍;演示链接:http://116.62.199.48:7070/《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/
新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍
截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观
面试考察点
-
概念理解:面试官想知道你是否真正理解 "预热" 这个概念在高并发场景下的本质,而不是只会背名词。预热的本质是什么?是把系统从冷态提前推到热态。
-
实战经验:是否经历过双 11、秒杀、大促这种高并发场景,知道在大流量来之前要做哪些准备。这块只有真干过大促的人才有体感。
-
系统性思维:能否从 JVM、缓存、连接池、服务发布等多个维度全面梳理预热的落地方式,而不是只说出一个点。
核心答案
预热(Warmup / Prewarm),说白了就是在正式流量到来之前,提前把系统从 "冷状态" 推到 "热状态"。
什么是 "热状态"?就是系统已经准备好随时承接大流量了:热点代码被 JIT 编译成了机器码,热点数据已经进缓存了,连接池里的连接也建好了,核心线程也拉起来了,依赖服务之间的长连接也握完手了。
一句话总结:让一切就绪,避免冷启动。
预热的典型场景有这几类:
| 预热类型 | 解决的问题 | 典型手段 |
|---|---|---|
| JVM 预热 | JIT 还没编译热点代码,解释执行性能差 | 跑核心接口、关闭分层编译用 CompileThreshold 调小 |
| 缓存预热 | 首次请求打到 DB,可能压垮数据库 | 启动时加载热点数据到 Redis |
| 连接池预热 | 首次请求才建连接,耗时高 | initialSize / minimum-idle |
| 线程池预热 | 核心线程没创建,请求来了才现建 | prestartAllCoreThreads() |
| 服务发布预热 | 新实例刚上线性能差就接全量流量 | 小流量灰度 + 健康检查 |
| 秒杀/大促预热 | 库存、商品数据需提前加载 | 活动前批量加载到 Redis |
深度解析
一、为什么需要预热——冷启动到底有多痛
先看一个典型的 "冷启动翻车" 场景:
上图展示的就是典型的冷启动雪崩链路。系统刚启动那会儿,所有东西都是 "冷" 的:JIT 还没编译热点代码,响应慢个几倍甚至十几倍;缓存里空空如也,请求全都穿透到 DB;连接池里没连接,排队建连接很耗时;线程池里没线程,临时建线程开销也大。
这时候如果有大流量瞬间涌入,几个问题会叠加:
- JVM 层面:解释执行比编译执行慢 5~10 倍,单个请求的处理时间成倍增加
- DB 层面:缓存为空,所有请求直接打穿到数据库,DB 瞬间被压垮
- 线程层面:连接池打满、线程池排队,请求开始堆积
- 雪崩效应:上游超时重试,进一步放大流量,最终整个系统挂掉
预热就是要把这些 "冷" 的东西提前变 "热",让流量进来时一切就绪。
二、JVM 预热——JIT 编译器的痛
JVM 是典型的 "解释执行 + JIT 编译" 混合模式。代码先被解释执行(慢),当某个方法被调用次数达到阈值时,JIT 才会把它编译成本地机器码(快)。
关于阈值有几个细节:
-XX:CompileThreshold:C2(server 编译器)默认 10000 次,C1(client 编译器)默认 1500 次。但这个默认值只有在关闭分层编译(-XX:-TieredCompilation)时才生效。- 分层编译(
-XX:+TieredCompilation,JDK 8 默认开启):JVM 不再使用单一阈值,而是用一组分层阈值(如Tier4InvocationThreshold、Tier4BackEdgeThreshold)来决定何时从解释执行 → C1 → C2 逐级升级。
所以 Java 应用刚启动时,前几千次调用都是解释执行,性能比稳定运行时差好几倍。这就是为什么很多公司上线后会先跑一段时间核心接口,或者用流量回放工具把核心链路压一遍,让 JIT 把热点代码编译好。
三、缓存预热——避免缓存雪崩
缓存预热是高并发场景最常考的预热场景。秒杀、大促这种活动,如果等用户请求来了才去 DB 查数据放缓存,第一个洪峰就会把 DB 压垮。
典型做法:
@PostConstruct
public void preloadCache() {
log.info("开始预热秒杀商品缓存...");
List<Item> hotItems = itemMapper.findHotItems();
for (Item item : hotItems) {
String key = "item:" + item.getId();
redisTemplate.opsForValue().set(key, JSON.toJSONString(item),
1, TimeUnit.HOURS);
// 同时预热布隆过滤器,防止缓存穿透
bloomFilter.put(item.getId());
}
log.info("缓存预热完成,共加载 {} 条", hotItems.size());
}
这段代码做了两件事:把热点商品数据预加载到 Redis,同时把商品 ID 放进布隆过滤器,防止恶意请求穿透缓存打 DB。
四、连接池 / 线程池预热
这一块经常被忽视,但其实挺关键。
数据库连接池(以 HikariCP 为例):
spring:
datasource:
hikari:
minimum-idle: 10 # 最小空闲连接数,启动即创建
maximum-pool-size: 50
connection-timeout: 30000
minimum-idle 配置后,HikariCP 启动时就会创建对应数量的连接放入池中。如果没配,第一个请求来了才建连接,TCP 三次握手 + 认证 + SSL 握手,单次几十毫秒甚至上百毫秒的开销。
HikariCP 官方还有个建议:把 minimum-idle 设成和 maximum-pool-size 一样,让连接池固定大小,避免运行期频繁建连销毁连接。
线程池预热:
ThreadPoolExecutor pool = new ThreadPoolExecutor(
corePoolSize, maxPoolSize, keepAliveTime,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000)
);
// 预创建所有核心线程,避免请求来了才建线程
pool.prestartAllCoreThreads();
prestartAllCoreThreads() 这个方法很多同学不知道,它会在创建完线程池后立刻把所有核心线程建好。方法返回值就是成功创建的线程数。秒杀场景特别有用。
五、服务发布预热——灰度引流
服务发布也是预热的高频场景。新版本上线时,JVM 是冷的、缓存是空的、连接是空的,如果直接接入全量流量,第一个洪峰就把新实例打挂了。
典型做法(K8s 场景):
这套机制的核心思路是:先让新实例接小流量跑热,再逐步放量。
liveness:进程是否存活(能不能被重启)readiness:是否准备好接流量(能不能进负载均衡)- 灰度放量:用网关权重、Dubbo 路由规则等控制流量比例
阿里内部有一套叫 "微服务预热" 的机制,新 Provider 上线后会先用极小流量跑一段时间,等 RT、CPU 稳定了再放全量,效果很明显。
面试高频追问
-
追问一:预热和懒加载矛盾吗?
不矛盾。懒加载是 "用到才加载",目的是加快启动速度;预热是 "提前加载热点",目的是避免冷启动。两者可以结合:启动时懒加载保证快速可用,跑起来后用定时任务预热热点数据。
-
追问二:预热加载太多数据会不会内存爆炸?
会的,所以预热要选热点、设过期、限数量。一般用 LRU 算法的本地缓存,或者 Redis 的
maxmemory-policy配合淘汰策略。同时监控预热后的内存使用,别把 JVM 堆撑爆。 -
追问三:JVM 预热有没有更精细的手段?
早期有 JEP 295 AOT 编译(Java 9 引入的实验性特性),但这个在 JDK 17 已经被 JEP 410 移除了。现代 Java 的 AOT 方案是 GraalVM Native Image,把 Java 代码在构建期直接编译成原生可执行文件,启动几乎是毫秒级。Spring 6 + Spring Boot 3 也原生支持了 GraalVM Native Image,是目前主流的 "去掉冷启动" 方案。
常见面试变体
- "秒杀系统为什么要预热?怎么预热?"
- "缓存雪崩和预热有什么关系?"
- "Java 服务刚启动为什么慢?怎么优化?"
- "K8s 的 liveness 和 readiness 探针有什么区别?跟预热啥关系?"
记忆口诀
预热 = 把冷的变热的:JIT 热点编译、缓存先填好、连接池先建好、线程先拉起、流量先小后大。
总结
预热是高并发系统的 "战前准备"。核心就一句话:别让流量撞上冷启动。从 JVM、缓存、连接池、线程池到服务发布,能预热的地方不少,每一处提前做一点,系统就稳一点。能在面试里把这几层都讲清楚,加分稳稳的。
