加快弹簧启动时间

我有一个 Spring Boot 应用程序。我添加了很多依赖项(不幸的是,看起来我需要所有这些依赖项) ,启动时间增加了很多。做 SpringApplication.run(source, args)只要10秒钟。

虽然与“习惯”相比,这可能不算什么,但我对这么做感到不高兴,主要是因为它打破了开发流程。此时应用程序本身相当小,所以我假设大多数时间是与添加的依赖关系相关的,而不是应用程序类本身。

我假设问题是类路径扫描,但我不确定如何:

  • 确认这是问题所在(即如何“调试”Spring Boot)
  • 如果它真的是原因,我怎样才能限制它,让它变得更快?例如,如果我知道某个依赖项或包不包含 Spring 应该扫描的任何内容,有没有办法限制它?

我认为 增强 Spring 在启动时具有并行 bean 初始化可以加快速度,但是增强请求从2011年就开始了,没有任何进展。我在 SpringBoot 本身中看到了一些其他的努力,比如 调查 Tomcat JarScanning 的速度改进,但是这是 Tomcat 特有的,已经被放弃了。

这篇文章:

虽然针对集成测试,建议使用 lazy-init=true,但是我不知道如何使用 Java 配置将其应用到 Spring Boot 中的所有 bean-这里有什么指示吗?

欢迎任何(其他)建议。

143071 次浏览

对我来说,这听起来像你使用了一个错误的配置设置。 首先检查 myContainer 和可能的冲突。 要确定谁使用了最多的资源,您必须检查内存映射(参见数据量!)每次为每个依赖项-这需要大量的时间,以及... (和 SUDO 特权)。 顺便问一下: 您通常是根据依赖项测试代码吗?

SpringBoot 做了很多可能不需要的自动配置。因此,您可能只想缩小您的应用程序所需的自动配置范围。要查看包含的自动配置的完整列表,只需在 DEBUG 模式下运行 org.springframework.boot.autoconfigure的日志记录(application.properties中的 logging.level.org.springframework.boot.autoconfigure=DEBUG)。另一个选项是使用 --debug选项运行弹簧启动应用程序: java -jar myproject-0.0.1-SNAPSHOT.jar --debug

在输出中会出现这样的情况:

=========================
AUTO-CONFIGURATION REPORT
=========================

检查这个列表,只包括你需要的自动配置:

@Configuration
@Import({
DispatcherServletAutoConfiguration.class,
EmbeddedServletContainerAutoConfiguration.class,
ErrorMvcAutoConfiguration.class,
HttpEncodingAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class,
JacksonAutoConfiguration.class,
ServerPropertiesAutoConfiguration.class,
PropertyPlaceholderAutoConfiguration.class,
ThymeleafAutoConfiguration.class,
WebMvcAutoConfiguration.class,
WebSocketAutoConfiguration.class,
})
public class SampleWebUiApplication {

代码是从 这篇博文复制的。

正如这个问题/答案中所描述的,我认为最好的方法是不要只添加那些你认为你需要的,而是排除那些你知道你不需要的依赖。

见: 减少春季启动时间

总之:

您可以看到底发生了什么,并启用调试日志记录,只需在从命令行启动应用程序时指定—— debug 即可。还可以在 application.properties 中指定 debug = true。

此外,您可以将 application.properties 中的日志记录级别简单地设置为:

Web: DEBUG Hibernate: ERROR

如果检测到不需要的自动配置模块,则可以禁用该模块。这方面的文件可以在这里找到: http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#using-boot-disabling-specific-auto-configuration

例如:

@Configuration
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class MyConfiguration {
}

到目前为止,投票最多的答案并没有错,但它没有深入到我想看到的程度,也没有提供任何科学证据。Spring Boot 团队进行了一次练习,以减少 Boot 2.0的启动时间,票据 11226包含了很多有用的信息。还有一个票据 7939打开添加时间信息的条件评估,但它似乎没有一个具体的 ETA。

调试启动最有用、最有方法的方法已经由 DaveSyer.https://github.com/dsyer/spring-boot-startup-bench完成

我也有一个类似的用例,所以我采用了 Dave 用 JMH 进行微基准测试的方法并运行它。其结果是 启动基准项目。我将它设计成可以用来测量任何 Spring Boot 应用程序的启动时间,使用由 bootJar(以前在 Boot 1.5中称为 bootRepackage) Gradle 任务生成的可执行 jar。请随意使用它并提供反馈。

我的调查结果如下:

  1. CPU 很重要,非常重要。
  2. 使用 - X 核实: 没有启动 JVM 非常有帮助。
  3. 排除不必要的自动配置会有所帮助。
  4. Dave 建议使用 JVM 参数 - XX: TieredStopAtLevel = 1,但是我的测试并没有显示出明显的改进。此外,-XX:TieredStopAtLevel=1可能会放慢您的第一个请求。
  5. 报告的主机名分辨率慢,但我没有发现这是一个问题,我测试的应用程序。

如果您试图优化手动测试的开发周转,我强烈推荐使用 Devtools

使用 spring-boot-devtools 的应用程序将自动重新启动 只要类路径上的文件发生更改。

只要重新编译——服务器就会自动重新启动(对于 Groovy,您只需要更新源文件)。如果你使用 IDE (比如“ vscode”) ,它可能会自动编译你的 Java 文件,所以只要保存一个 Java 文件就可以启动一个服务器重启,间接地——在这方面,Java 变得和 Groovy 一样无缝。

这种方法的美妙之处在于,增量重启可以缩短一些从头开始的启动步骤——这样您的服务就可以更快地备份和运行!


不幸的是,这对部署或自动化单元测试的启动时间没有帮助。

我觉得很奇怪,以前没有人提出这些优化。下面是一些在开发时优化项目构建和启动的一般技巧:

  • 从防病毒扫描器中排除开发目录:
    • 项目目录
    • 构建输出目录(如果它在项目目录之外)
    • IDE 索引目录(例如 ~/. IntelliJIde2018.3)
    • 部署目录(Tomcat 中的 webapps)
  • 升级硬件。使用更快的 CPU 和 RAM,更好的 Internet 连接(用于下载依赖项)和数据库连接,切换到 SSD (现在 NVMe SSD 是性能最好的存储器)。视频卡不重要。
  • 使用最新的 Gradle 和 JVM 版本。
  • 通过使用更多的并发进程,并行构建可以显著减少整体构建时间。

警告

  1. 第一种选择是以降低安全性为代价的。
  2. 第二种选择需要花钱(很明显)。

警告: 如果您不使用 Hibernate DDL 自动生成 DB 模式,也不使用 L2缓存,那么这个答案不适用于您。往前滚。

我的发现是 Hibernate 为应用程序的启动增加了大量的时间。禁用 L2缓存和 数据库初始化可以加快 SpringBoot 应用程序的启动速度。在生产环境中保留缓存为 ON,并在开发环境中禁用它。

项目名称:

spring:
jpa:
generate-ddl: false
hibernate:
ddl-auto: none
properties:
hibernate:
cache:
use_second_level_cache: false
use_query_cache: false

测试结果:

  1. L2缓存开启,ddl-auto: update:54秒

     INFO 5024 --- [restartedMain] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 23331 ms
    INFO 5024 --- [restartedMain] b.n.spring.Application : Started Application in 54.251 seconds (JVM running for 63.766)
    
  2. L2缓存关闭,ddl-auto: none:32秒

     INFO 10288 --- [restartedMain] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 9863 ms
    INFO 10288 --- [restartedMain] b.n.spring.Application : Started Application in 32.058 seconds (JVM running for 37.625)
    

又多了22秒! 现在我想知道这些空闲时间我该做些什么

Spring Boot 2.2. M1 增加了一些特性来支持 Spring Boot 中的惰性初始模式。

默认情况下,当刷新应用程序上下文时,将创建上下文中的每个 bean 并注入其依赖项。相比之下,当一个 bean 定义被配置为惰性初始化时,它不会被创建,它的依赖关系在需要时才会被注入。

启用惰性初始模式 设置 spring.main.lazy-initialization为 < strong > true

何时启动惰性初始模式

惰性初始模式可以显著改善启动时间,但也有一些明显的缺点,重要的是要小心使用

详情请查询 医生

更新:

SpringBootSpring2.4.0-启动端点

SpringBoot2.4.0添加了一个新的 Startup 端点,可用于识别启动时间超过预期的 bean。 您可以在这里获得有关应用程序启动跟踪的更多细节 < a href = “ https://docs.spring.io/spring-Framework/docs/5.3.x/reference/html/core.html # context-function-start”rel = “ norefrer”>

这里描述了一系列可能的行动: https://spring.io/blog/2018/12/12/how-fast-is-spring

我会把最重要的音符放在 Spring 旁边(稍作调整) :

  • 从 Spring Boot 启动器中排除类路径:
    • Hibernate Validator
    • Jackson (但是 Spring Boot 执行器依赖于它)。如果需要 JSON 渲染,可以使用 Gson (只适用于开箱即用的 MVC)。
    • Logback: 改为使用 slf4j-jdk14
  • 使用 spring-context-indexer。它不会添加太多内容,但是每一点都会有帮助。
  • 如果你能负担得起,就不要使用执行器。
  • 使用 Spring Boot 2.1和 Spring 5.1。当它们可用时切换到2.2和5.2。
  • spring.config.location(命令行参数或系统属性等)修复 SpringBoot 配置文件的位置。IDE 中的测试示例: spring.config.location=file://./src/main/resources/application.properties
  • 如果在 spring.jmx.enabled=false中不需要 JMX,请关闭它(这是 Spring Boot 2.2中的默认设置)
  • 在 Spring Boot 2.2中有一个新的标志 spring.main.lazy-initialization=true(对于旧的 Spring 使用 LazyInitBeanFactoryPostProcessor)。
  • 解压这个胖罐子并使用一个显式的类路径运行。
  • -noverify运行 JVM。还要考虑 -XX:TieredStopAtLevel=1(它会减慢以后的 JIT 速度,以节省启动时间为代价)。

上面提到的 LazyInitBeanFactoryPostProcessor(如果您不能应用 Spring 2.2中提供的标志 spring.main.lazy-initialization=true,那么您可以在 Spring 1.5中使用它) :

public class LazyInitBeanFactoryPostProcessor implements BeanFactoryPostProcessor {


@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
definition.setLazyInit(true);
}
}
}

您还可以使用(或编写自己的——这很简单)一些东西来分析 bean 的初始化时间: https://github.com/lwaddicor/spring-startup-analysis

希望能有帮助!

对我来说,断点太多了。当我单击“静音断点”并在调试模式下重新启动应用程序时,应用程序启动速度提高了10倍。