SpringBoot 为何启动慢
想获取更多高质量的Java技术文章?欢迎访问Java技术小馆官网,持续更新优质内容,助力技术成长
Java技术小馆官网https://www.yuque.com/jtostring
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 生产环境实测对比
环境 | 启动时间 | 已加载 Bean 数量 | 线程池状态 |
开发环境 | 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 函数
免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。