测试应用:内部有个springboot服务Order-System,依赖有数据库、redis、mq。启动耗时:
Started NeoOrderSystemApplication in 81.579 seconds
(1)先问下chatGpt
提问:springboot 启动慢怎么优化?
Spring Boot启动慢的原因可能有很多,以下是一些常见的优化建议:
优化依赖管理
依赖管理是影响Spring Boot启动时间的主要因素之一。建议使用合适的版本和精简的依赖
来避免不必要的加载。在pom.xml文件中,可以排除不需要的依赖和模块。
启用Spring Boot的自动配置
Spring Boot提供了自动配置功能,它可以帮助我们更快地构建应用程序并节省时间。建议在项目中开启自动配置,并仔细检查是否有不必要的自动配置类。
减少扫描路径
Spring Boot会自动扫描整个classpath,以寻找服务和组件。这可能会导致启动缓慢的问题。建议尽可能减少扫描路径,只扫描必要的包和类。
使用懒加载
使用懒加载可以延迟Bean的初始化,从而减少启动时间。例如,在@Configuration注释上添加@Lazy注释以将Bean定义为懒加载。
配置线程池
在处理大量请求时,线程池可能会成为瓶颈。通过适当地配置线程池,可以使应用程序更好地利用系统资源。建议将线程池大小设置为适当的值,以避免过多或过少的线程。
在启动应用程序时,Spring Boot会预热缓存以提高性能。但是,如果缓存过大或数据量过于庞大,则会导致启动时间延长。建议使用适当的缓存策略,并配置合适的缓存大小。
启用日志级别调整
根据需要,可以将日志级别调整为更高或更低的级别。较高的级别可能会导致启动缓慢,而较低的级别可能会导致无法查看重要的信息。建议根据需要进行调整。
通过以上优化建议,可以显著减少Spring Boot的启动时间,并提高应用程序的性能和稳定性。
(2)思考
有可行性的有:
第一点:依赖管理,这个maven依赖分析可以看看;
第三点:减少扫描路径;
第四点:使用懒加载
先易后难,先试下懒加载
(1)懒加载
问下chatGpt:springboot 懒加载
Spring Boot 中的懒加载是指在 Bean 实例化时,仅在需要使用该 Bean 的时候才会进行初始化。这种方式可以减少启动时的负载和时间,并提高应用程序的性能。
在 Spring Boot 应用程序中,可以通过以下的方式将 Bean 设置为懒加载:
1.在 @Bean 注解上添加 @Lazy 注解
2.在 application.properties 或 application.yml 文件中配置全局懒加载
spring.main.lazy-initialization=true
以上是几种常见的设置 Spring Boot Bean 懒加载的方式。在实际开发中,可以根据具体需求选择合适的方式进行设置。
Started NeoOrderSystemApplication in 56.148 seconds
泰裤辣!81s下到了56s,有点夸张了。
(2)减少扫描路径
先PUA下chatGpt:springboot 减少扫描
在 Spring Boot 中,使用 @SpringBootApplication 或 @ComponentScan 注解扫描包的时候,默认会扫描整个类路径(classpath)。扫描路径较大会导致启动缓慢的问题。因此,我们可以通过以下方式来减少扫描路径:
1.将启动类放在根包下
2.在 @ComponentScan 注解上指定扫描路径
3.使用 excludeFilters 和 includeFilters 过滤特定的组件
总之,在实际开发中,我们应该尽可能减少扫描路径,这样可以有效地提高 Spring Boot 应用程序的启动速度和性能。
但是看了项目,以上几点项目已经基本做到了,搜索引擎查下springboot加载优化,看到有个spring-context-indexer的技术,maven配置:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<optional>true</optional>
</dependency>
Started NeoOrderSystemApplication in 21.435 seconds
效果还可以,56s下到了21s.
Spring 5 启动性能优化之 @Indexed
(3)减少依赖
提问chatGpt:maven 去掉不用的依赖
在 Maven 项目中,可以使用以下两种方式去掉不用的依赖:
1. 使用 Maven Dependency Plugin 分析依赖关系
> $ mvn dependency:analyze
Maven Dependency Plugin 输出的结果会列出哪些依赖项未被使用,例如:
[INFO] The following dependencies were unused:
[INFO] com.example:unused:jar:1.0-SNAPSHOT
然后,我们可以在 pom.xml 文件中移除这些依赖项。
2. 使用 Maven Enforcer Plugin 强制执行依赖项规则
Maven Enforcer Plugin 可以强制执行 Maven 项目中的一些规则,包括禁止使用某些依赖项。可以通过在 pom.xml 文件中配置 enforcer 规则来实现.
总之,在开发过程中,我们应该及时清理不用的依赖项,以避免对项目性能和安全造成影响。
试了下,不过结果有点不准,有些依赖是间接用上的,maven检测不出来。
三、观察和尝试
上面这些方法都是全局优化的,针对具体的加载逻辑,有那些耗时呢?下面介绍几个观察的方式。
(1)日志记录bean加载时间
spring加载bean有那些耗时操作?结合spring加载生命周期,实现BeanPostProcessor接口可以统计时间。
postProcessBeforeInitialization 记录开始时间
postProcessAfterInitialization 记录结束时间
import com.alibaba.fastjson.JSON;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.list.UnmodifiableList;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.*;
@Slf4j
@Component
public class BeanInitMetrics implements BeanPostProcessor, CommandLineRunner {
private Map<String, Long> stats = new HashMap<>();
private List<Metric> metrics = new ArrayList<>();
@Override
public void run(String... args) throws Exception {
* 启动完成之后打印时间
List<Metric> metrics = getMetrics();
log.info(JSON.toJSONString(metrics));
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
stats.put(beanName, System.currentTimeMillis());
return bean;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Long start = stats.get(beanName);
if (start != null) {
metrics.add(new Metric(beanName, Math.toIntExact(System.currentTimeMillis() - start)));
return bean;
public List<Metric> getMetrics() {
metrics.sort((o1, o2) -> {
try {
return o2.getValue() - o1.getValue();
} catch (Exception e) {
return 0;
log.info("metrics {}", JSON.toJSONString(metrics));
return UnmodifiableList.unmodifiableList(metrics);
@Data
public static class Metric {
public Metric(String name, Integer value) {
this.name = name;
this.value = value;
this.createDate = new Date();
private String name;
private Integer value;
private Date createDate;
得到bean时间日志,这几个xxxEntityManagerFactory是和数据库资源相关的:
[ { "createDate": 1686846750336, "name": "liansEntityManagerFactory", "value": 6410 }, { "createDate": 1686846750352, "name": "arkEntityManagerFactory", "value": 3385 }, { "createDate": 1686846750284, "name": "insuranceEntityManagerFactory", "value": 1669 }]
1.这部分看起来是没有做懒加载也没有做异步加载。异步加载bean技术,SOFA提供了一个方案: 使用线程处理init方法。
2.功能优化:部分功能其实已经业务过期了,无用的数据库表要下掉。这部分需要结合具体业务删除代码。
去掉无关的数据库调用对象,时间:
Started NeoOrderSystemApplication in 12.068 seconds
(2)启动日志分析
这里我没有看springboot的详细启动源码,直接从日志看springboot 启动事件,看到做的事情有:
1-加载banner
2-spring data 扫描数据源
3-Tomcat 容器启动
4-加载Jpa的映射库dao/Repository
5-MVC 启动
6-MQ 消费者
开启debug级别日志,看日志有很多时间是耗费在4-加载Jpa的映射库dao/Repository,这个在jprofiler也能看到。看了一些文章得到的建议是:
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
spring.data.jpa.repositories.bootstrap-mode=lazy
删除用重复的QuerydslPredicateExecutor接口
Started NeoOrderSystemApplication in 9.949 seconds
第一次突破10s大关!
Speed up Spring Boot Startup
记一次JPA项目启动速度优化
(3)方法级别的观察:Profile
打印火焰图,用async-profile打印火焰图,可以通过idea或者arthas操作,不过我是windows系统,用的jprofiler查看cpu viewer:
图中看到耗时主要是:一个是Hibernate/Jpa创建字节码,另一个是阿里云MQ初始化。
windows下idea里的profile是基于jfr的,以下是chatGpt的jfr介绍和使用。
提问chatGpt:JFR使用
Java Flight Recorder(JFR)是 JDK 自带的一个性能分析工具,可以捕获应用程序执行期间的数据和事件。JFR 通常会记录以下类型的数据:
CPU 使用率、线程数、内存使用量等系统指标。
方法调用、锁状态、IO 操作等运行时信息。
垃圾收集信息、类加载情况等 JVM 相关信息。
JFR 可以生成丰富的数据报告,从而帮助开发人员进行性能分析和故障排除。
以下是使用 JFR 的一般步骤:
1.启用 JFR
JFR 默认情况下未启用,需要在启动时显式地启用它。可以通过以下命令来启用 JFR:
java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder ...
其中 -XX:+UnlockCommercialFeatures 开启了商业特性,-XX:+FlightRecorder 启用了 JFR。
2.分析数据
一旦 JFR 数据收集完成,就可以使用 JFR 工具来分析数据了。JDK 自带了一个名为 jcmd 的命令行工具,可以用它来分析和导出 JFR 数据。例如,以下命令可以列出当前正在运行的 JVM 进程,并显示它们的 PID 和名称:
jcmd -l
要导出 JFR 数据,可以使用以下命令:
jcmd <PID> JFR.dump filename=<filename>
其中 <PID> 是 JVM 进程的 ID,<filename> 是导出文件的名称和路径。
3.使用 JMC
Java Mission Control(JMC)是一个用于监视和管理 Java 应用程序的工具套件,包括对 JFR 数据的查看和分析功能。可以使用 JMC 打开 JFR 数据文件,然后查看各种性能指标、方法调用和线程活动等信息。
以上是使用 JFR 的一般步骤。JFR 提供了丰富的性能分析和故障排除功能,可以帮助开发人员更好地优化应用程序的性能和稳定性。
在idea导航栏Run-->Run with Profiler,idea可能异常,需要设置启动参数-XX:+UnlockCommercialFeatures。得到下图:
还有GC事件数据:
在JMC中查看:
从2图中其实看不出什么,倒是看到初始化阿里云的MQ初始化有点慢,暂时还没有好的优化策略。至于GC 总时间是330多ms,优化意义不大。
四、一些尝试和坑
下面是优化过程中做的一些尝试和发现的坑。
(1)sofa 异步加载
试用了下还行,但是要改动代码,里面是针对对象创建做了初始化,需要配置异步并且指定一个init方法交给线程。
<dependency>
<groupId>com.alipay.sofa</groupId>
<artifactId>runtime-sofa-boot-starter</artifactId>
</dependency>
bean 配置,也可以注解:
<bean id="testBean" class="com.alipay.sofa.runtime.beans.TimeWasteBean" init-method="init" async-init="true"/>
参考:Spring Bean 异步初始化
(2)域名解析导致的延迟
数据库,redis,mq 是通过域名配置,windows有时候域名解析有问题,会导致加载时间延长。本地可以改host配置,或者直接改成ip连接,其他环境不用动。
参考:Windows下SpringBoot启动非常慢排查经历
(3)避坑指南
1.在测试过程中有几点坑,首先是延迟加载会导致启动应用之后是部分请求触发了真正的加载,时间会牺牲在第一次请求这里。
2.spring-context-index 会有部分bean加载不到,比如swagger的配置。
这样优化Spring Boot,启动速度快到飞起!
回头看,做启动优化可能意义不大,因为很多技巧只适合在本地使用,但是,在这个过程中,涨了很多的知识。
了解spring-context-indexer技术
使用arthas\profile,打印火焰图,查看CPU\线程\内存\IO\堆\GC,使用Jprofiler和JMC工具分析程序性能
解构spring bean的生命周期,可以是在对应节点打印日志,可以是改写同步变异步,sofa 异步加载bean也是在生命周期做的处理。
有想法就去执行!
Tquan
粉丝