Spring Boot 启动慢排查与优化实战指南

本文总结 Spring Boot 启动耗时过长的常见原因,并给出一整套可落地的排查方案 ,包括启用 startup 端点分析、编写 Bean 初始化耗时记录器、输出 Top10 耗时 Bean 等,帮助快速定位并解决启动瓶颈问题。


📌 1. 启用 Actuator startup 端点

Spring Boot 2.4+ 引入了 ApplicationStartup 机制,可以记录启动各阶段耗时。

我们可以借助 spring-boot-starter-actuator/startup 端点来分析:

添加依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

配置 application.yml

这里我们将 Actuator 单独跑在 12306 端口上,路径为 /java

yaml 复制代码
management:
  server:
    port: 12306
  endpoints:
    web:
      base-path: /java
      exposure:
        include: '*'
  endpoint:
    startup:
      enabled: true

在启动类中设置 BufferingApplicationStartup

必须在 run() 之前设置

java 复制代码
@SpringBootApplication
public class ServiceApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(ServiceApplication.class);
        app.setApplicationStartup(new BufferingApplicationStartup(2048));
        app.run(args);
    }
}

访问启动耗时信息

应用启动后,访问:

复制代码
http://localhost:12306/java/startup

即可查看所有 Bean 初始化和自动配置加载的耗时信息。


📌 2. 打印 Bean 初始化耗时日志(实时查看)

访问 /startup 虽然可以分析启动,但需要打开浏览器。

我们可以编写一个 BeanPostProcessor,在启动时实时输出每个 Bean 的初始化耗时

java 复制代码
@Component
public class BeanStartupTimeLogger implements SmartInstantiationAwareBeanPostProcessor {

    private final ConcurrentHashMap<String, Long> startTimes = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, Long> durations = new ConcurrentHashMap<>();

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        startTimes.put(beanName, System.nanoTime());
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Long start = startTimes.remove(beanName);
        if (start != null) {
            long durationMs = (System.nanoTime() - start) / 1_000_000;
            durations.put(beanName, durationMs);
            System.out.printf("[Bean Init] %-60s %d ms%n", beanName, durationMs);
        }
        return bean;
    }

    /** 启动完成后打印 Top 10 */
    @EventListener(ContextRefreshedEvent.class)
    public void onContextRefreshed() {
        System.out.println("\n================= Bean 初始化耗时 Top 10 =================");
        durations.entrySet().stream()
                .sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
                .limit(10)
                .forEach(e -> System.out.printf("%-60s %d ms%n", e.getKey(), e.getValue()));
        System.out.println("==========================================================\n");
    }
}

效果示例:

复制代码
[Bean Init] requestMappingHandlerMapping                       956 ms
[Bean Init] jacksonObjectMapper                                758 ms
[Bean Init] jdbcTemplate                                       586 ms
...
================= Bean 初始化耗时 Top 10 =================
requestMappingHandlerMapping                       956 ms
jacksonObjectMapper                                758 ms
jdbcTemplate                                       586 ms
...
==========================================================

📌 3. 常见启动慢的根因定位

根据统计出的 Top 耗时 Bean,通常可以快速推断原因:

耗时 Bean 典型原因 优化建议
requestMappingHandlerMapping Controller 数量多,包扫描范围大 限定 @ComponentScan 范围
jacksonObjectMapper 类/模块过多,加载慢 减少 @JsonComponent/@JsonMixin 模块
jdbcTemplate / transactionManager 初始化访问数据库,连接慢 配置连接池超时,延迟初始化
redisKeyValueTemplate / reactiveRedisTemplate 引入 Redis Repository 自动配置 如果未使用 Repository,可 exclude
openApiResource Springdoc-OpenAPI 初始化大量类 只在开发环境启用 swagger

📌 4. 通用优化策略

  • 使用 spring.main.lazy-initialization=true 推迟非必要 Bean 初始化
  • 精简 @ComponentScan 扫描包范围
  • 排除不必要的 Starter / AutoConfiguration
  • 检查数据库、Redis 等外部依赖的连接耗时(DNS/网络/超时)
  • 使用 JFR / VisualVM 等分析启动 CPU 时间

📌 5. 总结

通过 /startup + BeanStartupTimeLogger,可以精准捕捉每个 Bean 初始化耗时,定位 Spring Boot 启动慢的根因。

相比盲目猜测,这种方式:

  • 无侵入,随时打开关闭
  • 统计结果可排序分析
  • 适合持续优化启动性能
相关推荐
考虑考虑8 小时前
docker compose V2版本新属性
运维·后端·自动化运维
mldong8 小时前
一个 App,十三套后端:手机审批端 uni-jeeflow-app 开源了
java·架构
tqs_123459 小时前
MySQL RR隔离级别死锁|Gap间隙锁、临键锁,订单并发范围查询死锁根因方案
java
逆境不可逃10 小时前
Pi Agent 学习笔记:多个工具怎样并行执行
java
weixin_BYSJ198710 小时前
【计算机毕设】基于SpringBoot与Vue的文物保护档案管理系统08621
vue.js·spring boot·spring cloud·微服务·架构·django·课程设计
GreenTea10 小时前
7000 万 QPS、500 PB:OpenAI 如何用一个 Python 存储平台撑住 10 亿用户
后端·架构
Flynt10 小时前
Java 27 升级实测:默认值动得比新特性多,有个老参数会让 JVM 直接起不来
java·jvm·后端
Bs_MoneyMagnet11 小时前
基于springboot+vue的个人健康管理系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·vue3·springboot3·计算机毕业设计
GreenTea11 小时前
OpenAI Agents API 上手实测:一次调用把整个 agent loop 甩给 OpenAI
前端·后端·算法
星云API技术支持11 小时前
企业微信二次开发:群权限设置、成员管理与群资料维护的接口组合实践
java·前端·企业微信