SpringBoot 为何启动慢

想获取更多高质量的Java技术文章?欢迎访问 Java技术小馆官网,持续更新优质内容,助力技术成长!

SpringBoot 为何启动慢?

某天你刚写完一个 SpringBoot 接口,点击运行后盯着控制台发呆:"怎么还卡在 Tomcat started on port 8080?这启动速度也太慢了吧!" 此时你脑海中浮现出面试官的灵魂拷问:"SpringBoot 为什么启动慢?"

但真相是:当你在开发环境看到 "Started Application in 5.2 seconds" 时,实际上 SpringBoot 已经处于 "满载(Full Load)" 状态。 这个状态下的启动时间,和你在生产环境中看到的启动时间可能天差地别!

一、SpringBoot 启动慢?先看这三个关键数据

案例:一个普通项目的启动时间线

yaml 复制代码
2023-08-01 10:00:00.000 INFO  [main] o.s.b.StartupInfoLogger : Starting Application
2023-08-01 10:00:00.500 INFO  [main] o.s.c.s.ClassPathXmlApplicationContext : Refreshing...
2023-08-01 10:00:03.200 INFO  [main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2023-08-01 10:00:04.100 INFO  [main] o.s.b.w.e.tomcat.TomcatWebServer : Tomcat started on port 8080
2023-08-01 10:00:05.200 INFO  [main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService
2023-08-01 10:00:05.250 INFO  [main] o.s.b.StartupInfoLogger : Started Application in 5.25 seconds 

看起来耗时 5.25 秒?但其中有三个关键阶段:

  1. Classpath 扫描(0-0.5s)
  2. Bean 初始化(0.5-4.1s)
  3. 满载状态(4.1-5.25s)

开发环境 vs 生产环境实测对比

环境 启动时间 已加载 Bean 数量 线程池状态
开发环境 5.2s 150+ 完整初始化
生产环境 2.1s 80 延迟加载

结论:开发环境中的 "慢启动" 其实是满载状态的表现!

二、深挖 "满载" 的本质

1. SpringBoot 启动的三个阶段

scss 复制代码
public class SpringApplication {
    // 核心启动流程
    public ConfigurableApplicationContext run(String... args) {
        // 阶段1:环境准备(约20%时间)
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        
        // 阶段2:上下文加载(约50%时间)
        ConfigurableApplicationContext context = createApplicationContext();
        refreshContext(context);
        
        // 阶段3:满载阶段(约30%时间)
        afterRefresh(context, applicationArguments);
        stopWatch.stop();
        // 输出 Started Application in xxx seconds
    }
}

2. 开发环境为何更慢?

开发环境的特殊配置:

ini 复制代码
# application-dev.properties
spring.devtools.restart.enabled=true # 热部署
spring.jpa.show-sql=true            # 显示SQL
management.endpoints.web.exposure.include=* # Actuator全开

这些配置会导致:

  • 多加载 20% 的监控 Bean
  • 增加 15% 的类路径扫描
  • 初始化调试用线程池

3. 满载的三大特征

typescript 复制代码
// 1. 所有@Bean方法已执行
@Bean 
public DataSource dataSource() { // 此时已初始化完成
    return new HikariDataSource();
}

// 2. 所有CommandLineRunner已运行
@Component
public class InitRunner implements CommandLineRunner {
    @Override
    public void run(String... args) { // 该方法已执行
        // 初始化业务数据
    }
}

// 3. Tomcat线程池就绪
tomcat.getConnector().getExecutor() // 返回非空线程池

三、你的项目真的慢吗?

方法1:使用 Actuator 的启动时间端点

yaml 复制代码
# application.yml
management:
  endpoints:
    web:
      exposure:
        include: startup

请求 /actuator/startup 返回:

json 复制代码
{
  "springBootVersion": "3.1.2",
  "timelines": {
    "spring.beans.instantiate": {
      "startTime": "2023-08-01T10:00:00.500Z",
      "endTime": "2023-08-01T10:00:03.200Z",
      "duration": "PT2.7S"
    },
    "tomcat.start": {
      "startTime": "2023-08-01T10:00:03.200Z",
      "endTime": "2023-08-01T10:00:04.100Z",
      "duration": "PT0.9S" 
    }
  }
}

方法2:Bean 加载时间排序

scss 复制代码
@Autowired
private ApplicationContext context;

public void printBeanInitTimes() {
    ((AbstractApplicationContext) context)
        .getBeanFactory()
        .getBeanDefinitionNames()
        .stream()
        .map(name -> new AbstractMap.SimpleEntry<>(
            name, 
            ((RootBeanDefinition) context.getBeanDefinition(name))
                .getResourceDescription()))
        .sorted((e1, e2) -> Long.compare(
            getInitTime(e1.getKey()), 
            getInitTime(e2.getKey())))
        .forEach(e -> System.out.println(e.getKey() + " : " + getInitTime(e.getKey())));
}

输出示例:

yaml 复制代码
myDataSource : 1200ms
entityManagerFactory : 800ms
transactionManager : 400ms

四、让启动速度提升 300%

方案1:延迟加载(实测减少40%时间)

csharp 复制代码
# application.properties
spring.main.lazy-initialization=true # 全局延迟加载

// 或针对特定Bean
@Lazy
@Bean
public MyHeavyBean heavyBean() { ... }

方案2:砍掉不必要的自动配置

less 复制代码
// 手动排除自动配置类
@SpringBootApplication(exclude = {
    DataSourceAutoConfiguration.class,
    HibernateJpaAutoConfiguration.class
})

// 或使用条件注解
@Configuration
@ConditionalOnProperty(name = "app.feature.cache.enabled")
public class CacheAutoConfiguration { ... }

方案3:线程池延迟初始化

scss 复制代码
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(0); // 初始0线程
    executor.setMaxPoolSize(20);
    executor.setWaitForTasksToCompleteOnShutdown(true);
    executor.initialize(); // 首次任务提交时初始化
    return executor;
}

方案4:生产环境预热(Docker 实测效果)

bash 复制代码
# Dockerfile
FROM openjdk:17-jdk-slim
COPY target/app.jar /app.jar
# 预热命令(不暴露端口)
RUN java -Dserver.port=-1 -jar /app.jar --spring.main.lazy-initialization=true &
  sleep 30 && \
  pkill -f 'java.*app.jar'
# 正式启动
CMD ["java", "-jar", "/app.jar"]

五、三个黄金法则

法则1:区分环境配置

ini 复制代码
# application-prod.properties
spring.main.lazy-initialization=true
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
spring.devtools.restart.enabled=false

法则2:监控先行,优化在后

推荐工具:

  • SpringBoot Actuator(内置监控)
  • Java Flight Recorder(JVM 级分析)
  • Arthas(动态诊断)

法则3:接受合理的启动时间

不同场景的合理启动时间:

场景 可接受时间
Serverless 函数 <1s
微服务实例 5-10s
传统单体应用 20-30s
数据分析批处理任务 1-5min

六、GraalVM 原生镜像的降维打击

一个简单的对比测试:

bash 复制代码
# 传统JAR启动
java -jar app.jar → 4.1s

# 原生镜像启动
./app → 0.05s

实现步骤:

  1. 添加 GraalVM 依赖
xml 复制代码
<dependency>
    <groupId>org.graalvm.nativeimage</groupId>
    <artifactId>native-image-maven-plugin</artifactId>
    <version>22.3.1</version>
</dependency>
  1. 构建原生镜像
java 复制代码
mvn -Pnative native:compile

想获取更多高质量的Java技术文章?欢迎访问 Java技术小馆官网,持续更新优质内容,助力技术成长!

相关推荐
陆柒1455 小时前
从洋葱模型到 Elpis Core:我对 Node.js 服务端开发的理解
架构
懂软件的胡子个哥5 小时前
微信 API 消息回调怎么设计,才能避免丢消息和重复处理
运维·微信·架构·wechatapi·个人微信号二次开发
nvvas5 小时前
Java 入门(四):数组——数据的集合管理
java·开发语言
Lambert2816 小时前
AgentScope Java 从零(07):官方有权限引擎,我却在工具里写了个 if
java·后端·ai编程
Kyrie_kk6 小时前
Java--ThreadLocal线程副本(一)
java·后端
君顾16 小时前
本地城市低空经济管理系统定制:城市级低空运营平台架构与落地实战
java·开发语言·飞手
奈斯先生Vector6 小时前
当模型版本不断变化,RelayRouter 能否帮助 AI 应用摆脱深度绑定
android·java·人工智能·开源·aigc
用户8181870627467 小时前
第28章 消息积压应急处理实录:一次真实大促故障复盘
java·后端
纯音乐sjc7 小时前
SPI 通信协议详解:从四线制到 ADXL345
面试·嵌入式
宸津-代码粉碎机7 小时前
微服务线上踩坑复盘:接口超时、负载倾斜隐形问题根治方案(生产级配置)
java·大数据·人工智能·python·spring