Spring Boot 启动慢怎么排查?把启动、外部依赖和 readiness 拆开看
Spring Boot 一慢启动,最怕把所有现象都叫成“框架太重”。问题常常分成几段:端口没起来、端口起来了但服务还没 ready,或者本地和线上压根不是同一种慢法;把这几段拉开后,才更容易判断该查哪一层。
Spring Boot 启动慢,在本地也许只是开发体验问题;放到扩容、滚动发布和故障恢复里,它影响的就是可用性窗口。
难点不在“慢”这个词,而在于三种情况经常被混着说:端口没监听、端口起来但 readiness 不过、服务看似启动完成却还接不住流量。先把慢点钉在具体阶段,后面的日志和指标才有解释力。
如果日志已经说明问题不在启动阶段,而是服务起来后接口慢、CPU 高或 GC 抖动,就直接转去运行时排查 接口响应慢怎么排查?后端性能问题定位步骤,或者接 JVM 分支 Java 服务 CPU 高怎么排查:线上先看哪些证据更有用。
先确认慢在启动的哪一段
先拿下面这组现象对一下,分清卡的是启动链本身,还是 ready 前后的可用性窗口。
| 你现在看到的现象 | 更像什么 | 下一步 |
|---|---|---|
| 从启动命令到端口监听明显很慢 | 类加载、自动配置、Bean 初始化偏重 | 按本文把启动链拆开 |
| 端口已经起来,但 readiness 长时间不过 | 预热逻辑、外部依赖、健康检查边界问题 | 按本文把 ready 前后的时间线拆开 |
| 本地慢得明显,线上还好 | IDE、类路径、调试参数、本地依赖环境问题 | 把本地和线上拆开看 |
| 线上扩容慢、滚动发布拖很久 | 数据源、外部依赖、预热和探针策略问题 | 把扩容链和探针链拆开看 |
| 启动阶段 CPU 或 GC 也明显抬高 | 启动链路里存在较重初始化或对象分配压力 | 先拆启动链,再补看 JVM 指标 |
| 应用已经 ready,但只是运行时接口慢 | 问题已不在启动阶段 | 转去看运行时慢调用那条线 |
一、把“启动慢”具体到哪一段
处理启动慢时,先抄出 4 个时间点最有用:进程启动、端口监听、readiness 首次成功、实例真正开始稳定接流量。很多团队口中的“启动慢”其实不是同一种事:有的是端口迟迟起不来,有的是端口起来了但 readiness 卡着不过,也有的是服务 ready 了却因为预热没做完仍然接不住流量。时间线不拆开,后面看日志、看探针、看 Bean 初始化都会混。
Spring Boot 启动慢最怕一上来只报总耗时。至少把下面几个边界分清楚。
1. 端口未起来
更像下面这些方向:
- 类加载和组件扫描偏重
- 自动配置太多
- Bean 创建和初始化逻辑太重
- 数据源、ORM、配置解析在启动早期就卡住
这类问题的典型特点是:应用还没有真正进入“对外可服务”阶段,瓶颈主要在启动过程本身。
2. 端口已起来,但服务还不可用
更像下面这些方向:
- 启动后预热逻辑还没跑完
- 缓存、规则、字典、索引在后台加载
- 外部依赖初始化很慢
- readiness / health check 配置让实例长时间进不了可用状态
这类问题比“端口没起来”更容易误导,因为表面上看应用像是已经成功启动了,但业务角度它还不能稳定接流量。
3. 本地慢和线上慢,不要走同一套排查顺序
如果主要是本地启动慢,更该优先怀疑:
- IDE 启动方式
- 开发环境类路径太重
- 调试参数和热部署插件
- 本地数据库、Nacos、Redis 等依赖连接
如果主要是线上扩容慢,更该优先怀疑:
- 数据源建连和探活
- 配置中心、注册中心、消息中间件连接
- 启动预热逻辑
- readiness 与健康检查边界
所以更重要的问题不是“Spring Boot 为什么慢”,而是:到底慢在启动前半段、启动后半段,还是卡在可用性判定阶段。
二、第一轮重点看哪些现象
真正能帮你缩小范围的,不是总启动时间这一行,而是下面这几组现象。
1. 启动总耗时和时间拆段
先把这几件事记下来:
- 总启动时间是多少
- 是最近才开始慢,还是一直慢
- 每次都慢,还是偶发慢
- 哪个环境最明显
然后尽量把启动过程拆段看:
- 容器初始化前后
- 数据源和 ORM 初始化
- 外部依赖连接
- 业务预热与 ready 前等待
2. 启动日志的时间点
把日志时间点抄出来最有价值。
你真正想从日志里确认的是:
- 卡在 Spring 容器初始化前后
- 卡在数据源或 ORM 初始化
- 卡在 Redis、配置中心、注册中心、MQ 等外部依赖
- 卡在某个自定义 Bean 或启动任务
这一层的目标不是先下结论,而是把“总共 20 秒”拆成“哪 8 秒最值得先查”。
3. readiness 和 health check 行为
这一层很容易被漏掉,但线上很关键。
要看:
- 端口监听和 readiness 成功之间隔了多久
- 探针失败是因为应用真的还没准备好,还是检查路径太重
- 是启动慢,还是启动后可用判定太严格
- 应用 ready 之前是否跑了阻塞式预热逻辑
如果你不先看这层,很容易把“服务已启动但尚不可用”误判成“Spring Boot 启动本身很慢”。
4. 本地与线上差异
很多启动慢问题只有在某一个环境里明显。
更要先对比:
- 本地和线上的类路径、依赖数量是否一致
- 本地是否开了调试参数、热部署、IDE agent
- 线上是否多了配置中心、注册中心、探针、Sidecar
- 启动参数、资源限制、容器规格是否一致
三、推荐排查顺序
如果你已经遇到 Spring Boot 启动慢,可以按下面这个顺序收窄范围,而不是一上来就乱翻配置。
先判断是“端口未起来”还是“端口已起来但不可用”
这是整条排查顺序里最重要的一刀。
- 端口未起来:优先查框架初始化、自动配置、Bean 初始化、数据源建连
- 端口已起来但不可用:优先查预热逻辑、外部依赖、健康检查和 readiness 边界
如果这一步不先切开,后面很多证据都会混在一起。
再看启动日志,把时间花在哪一段拆出来
重点不是只看这行:
Started Application in 18.532 seconds
更重要的是启动过程中的各段日志时间点。只要日志打得足够清楚,很多问题根本不用先上 Profiler,就已经能缩到具体阶段了。
第三步:如果日志还不够细,再打开启动分析能力
Spring Boot 自带的这些能力都很值得用:
--debugApplicationStartup- Actuator 启动指标
它们最有价值的不是“输出更多信息”,而是帮你确认:
- 哪些自动配置生效了
- 哪些 Bean 初始化特别重
- 哪些生命周期阶段耗时明显偏高
第四步:按最大耗时段继续下钻
如果最大耗时落在下面这些方向,后续思路会不同:
- 自动配置 / 组件扫描偏重
- Bean 初始化逻辑过重
- 数据源、连接池、ORM 初始化慢
- 外部依赖连接慢
- 业务预热和 readiness 阶段卡住
第五步:最后再决定优化动作
只有在前面证据足够明确之后,才去决定:
- 缩扫描范围
- 清理 starter
- 延迟初始化
- 异步预热
- 调整建连和探活策略
- 改健康检查与 readiness 边界
四、这类问题背后的常见根因
根因 1:自动配置和组件扫描太重
Spring Boot 的便利来自默认自动配置,但项目一大,这也会变成启动成本。常见场景是:
- 引入了很多用不到的 starter
- 扫描范围过大
- Mapper、Entity、配置类扫得太宽
- 条件装配判断过多
如果日志显示还没进入业务初始化,就已经花掉了大量时间,这条线很值得优先查。
根因 2:Bean 初始化逻辑太重
这类问题在真实项目里非常高频,而且通常比“框架本身重”更真实。
要重点看:
@PostConstructInitializingBeanCommandLineRunnerApplicationRunner- 自定义
BeanPostProcessor/BeanFactoryPostProcessor
如果这些位置做了查库、调 HTTP、加载大配置、预热缓存、构建大对象图,Spring 容器就会被整体拖住。
根因 3:数据源、连接池和 ORM 初始化慢
线上扩容慢、启动窗口长,数据库相关初始化经常是高频根因。
更常见的场景有:
- 数据库建连慢、DNS 解析慢、连接重试多
- 连接池启动参数让应用在启动时预创建过多连接
- JPA 自动建表 / 校验结构较重
- MyBatis mapper 扫描和插件初始化偏重
根因 4:外部依赖把启动阶段整体拖长
现代 Spring Boot 项目很少只连数据库,通常还会连:
- Redis
- Kafka / RocketMQ
- Nacos / Apollo / 配置中心
- 注册中心
- 对象存储
- 远程服务或探活接口
这类问题很容易被误解成“应用自己慢”,但真实情况往往是:应用在等别的系统。
根因 5:预热逻辑和 readiness 设计不合理
这是线上最常见、也最容易和“启动慢”混淆的一类问题。
比如:
- 加载大字典表
- 全量缓存预热
- 规则引擎初始化
- 大量远程配置拉取
- 启动后必须跑完一批任务才算 ready
这类动作本身未必错,关键要问:它是否必须阻塞应用进入可用状态。
五、最容易误判的地方
- 端口没起来和端口起来但 readiness 不过,不是同一种问题,排查顺序也不一样
- 本地慢不等于线上也会慢,很多本地慢来自 IDE、类路径和开发依赖环境
- 线上扩容慢不一定是 Spring Boot 本身重,很多时候是数据源、配置中心、MQ、探针或预热链在拖
- 启动慢不一定都该靠调 JVM 参数解决,很多时候根因在 Bean 初始化和业务准备动作
- readiness 失败不一定代表应用没启动成功,也可能只是健康检查路径设计得太重或太早
六、FAQ:启动慢里最容易混掉的几件事
1. Spring Boot 启动慢时,应该先看日志还是先看代码?
默认先看日志。因为日志能最快把问题拆到具体阶段:是卡在容器初始化、数据源建连、外部依赖,还是卡在某个启动任务。先把阶段拆出来,再去看代码,效率会高很多。
2. 端口已经起来了,但实例还是接不住流量,这算启动慢吗?
更准确地说,这是“端口已起来,但服务尚未 ready”。这种场景更该优先查预热逻辑、外部依赖和健康检查边界,而不是只盯 Spring Boot 框架初始化。
3. 本地启动慢和线上启动慢,为什么排查方式不同?
因为两边约束不同。本地更容易受 IDE、类路径、热部署、调试参数和本地依赖环境影响;线上更容易受配置中心、注册中心、数据库建连、探针和容器资源限制影响。
4. readiness 失败和启动慢,应该怎么区分?
如果端口监听已经完成,但实例迟迟不被流量接入,更多是 readiness 边界问题;如果应用连端口都没监听起来,更像启动过程本身慢。前者更像“可用性判定慢”,后者才更像“启动链本身慢”。
5. 启动慢时,一上来就开 --debug 有必要吗?
如果普通日志已经能把阶段拆清楚,未必要一开始就开;但当你怀疑自动配置过重、条件装配过多,或者想更细地看启动步骤时,--debug、ApplicationStartup 和 Actuator 启动指标都很有价值。
七、已经确认不在启动链,就直接切出去
如果日志和时间线已经说明问题不在启动链本身,下面这些文章可以直接接着查:
- 问题已经不在启动阶段,而是运行时接口慢:
接口响应慢怎么排查?后端性能问题定位步骤 - 启动后数据源和数据库方向最可疑:
数据库连接池打满时,根因通常不是连接数太小 - 启动阶段 SQL 预热或查库逻辑明显偏重:
MySQL 慢查询怎么定位:从执行计划到真实瓶颈 - 怀疑是 Spring 代理、事务边界或 Bean 调用方式引起的初始化问题:
Spring 事务为什么会失效?常见场景汇总 - 启动阶段同时伴随 CPU 异常:
Java 服务 CPU 高怎么排查:线上先看哪些证据更有用 - 启动阶段同时伴随 GC 抖动和内存压力:
Full GC 频繁怎么办:先判断是不是内存泄漏
八、关键还是把慢点钉在时间线上
Spring Boot 启动慢,最怕的是把这些不同阶段的卡点都算进同一个“启动慢”。
先把端口监听、readiness 首次成功和稳定接流量这几个时间点摊开,再回头看自动配置、Bean 初始化、外部依赖或预热逻辑,后面的优化动作才不会变成盲调。