SpringBoot 为何启动慢
想获取更多高质量的Java技术文章?欢迎访问 Java技术小馆官网,持续更新优质内容,助力技术成长!
SpringBoot 为何启动慢?
某天你刚写完一个 SpringBoot 接口,点击运行后盯着控制台发呆:"怎么还卡在 Tomcat started on port 8080?这启动速度也太慢了吧!" 此时你脑海中浮现出面试官的灵魂拷问:"SpringBoot 为什么启动慢?"
但真相是:当你在开发环境看到 "Started Application in 5.2 seconds" 时,实际上 SpringBoot 已经处于 "满载(Full Load)" 状态。 这个状态下的启动时间,和你在生产环境中看到的启动时间可能天差地别!
一、SpringBoot 启动慢?先看这三个关键数据
案例:一个普通项目的启动时间线
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 秒?但其中有三个关键阶段:
- Classpath 扫描(0-0.5s)
- Bean 初始化(0.5-4.1s)
- 满载状态(4.1-5.25s)
开发环境 vs 生产环境实测对比
开发环境 | 5.2s | 150+ | 完整初始化 |
生产环境 | 2.1s | 80 | 延迟加载 |
结论:开发环境中的 "慢启动" 其实是满载状态的表现!
二、深挖 "满载" 的本质
1. SpringBoot 启动的三个阶段
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. 开发环境为何更慢?
开发环境的特殊配置:
# application-dev.properties spring.devtools.restart.enabled=true # 热部署 spring.jpa.show-sql=true # 显示SQL management.endpoints.web.exposure.include=* # Actuator全开
这些配置会导致:
- 多加载 20% 的监控 Bean
- 增加 15% 的类路径扫描
- 初始化调试用线程池
3. 满载的三大特征
// 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 的启动时间端点
# application.yml management: endpoints: web: exposure: include: startup
请求 /actuator/startup
返回:
{ "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 加载时间排序
@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()))); }
输出示例:
myDataSource : 1200ms entityManagerFactory : 800ms transactionManager : 400ms
四、让启动速度提升 300%
方案1:延迟加载(实测减少40%时间)
# application.properties spring.main.lazy-initialization=true # 全局延迟加载 // 或针对特定Bean @Lazy @Bean public MyHeavyBean heavyBean() { ... }
方案2:砍掉不必要的自动配置
// 手动排除自动配置类 @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class }) // 或使用条件注解 @Configuration @ConditionalOnProperty(name = "app.feature.cache.enabled") public class CacheAutoConfiguration { ... }
方案3:线程池延迟初始化
@Bean public ThreadPoolTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(0); // 初始0线程 executor.setMaxPoolSize(20); executor.setWaitForTasksToCompleteOnShutdown(true); executor.initialize(); // 首次任务提交时初始化 return executor; }
方案4:生产环境预热(Docker 实测效果)
# 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:区分环境配置
# 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 原生镜像的降维打击
一个简单的对比测试:
# 传统JAR启动 java -jar app.jar → 4.1s # 原生镜像启动 ./app → 0.05s
实现步骤:
- 添加 GraalVM 依赖
<dependency> <groupId>org.graalvm.nativeimage</groupId> <artifactId>native-image-maven-plugin</artifactId> <version>22.3.1</version> </dependency>
- 构建原生镜像
mvn -Pnative native:compile
想获取更多高质量的Java技术文章?欢迎访问 Java技术小馆官网,持续更新优质内容,助力技术成长!
#springboot#