加快Spring Boot启动时间

176

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

尽管与我们通常的情况相比可能并不算多,但我对它花费那么长时间感到不满,主要是因为它破坏了开发流程。目前该应用程序本身相当小,因此我认为大部分时间都与添加的依赖项有关,而不是应用程序类本身。

我认为问题出在类路径扫描上,但我不确定如何:

  • 确认是否是该问题(即如何“调试”Spring Boot)
  • 如果真的是原因,我该如何限制它,以使它变快?例如,如果我知道某个依赖项或包不包含Spring应该扫描的任何内容,是否有一种方式可以限制它?

我认为增强Spring以在启动期间具有并行bean初始化将加快速度,但该增强请求自2011年以来一直处于打开状态,没有任何进展。我在Spring Boot本身中看到了一些其他努力,例如研究Tomcat JarScanning速度改进,但这是特定于Tomcat的,并且已经被放弃。

这篇文章:

虽然针对集成测试,但建议使用lazy-init=true,但我不知道如何在Java配置中将其应用于所有bean - 有什么提示吗?

欢迎任何(其他)建议。


1
发布你的代码。通常只有定义应用程序运行器的包被扫描。如果你为 @ComponentScan 定义了其他包,那么它们也会被扫描。另一件事是确保你没有启用调试或跟踪日志记录,因为通常日志记录很慢,非常慢。 - M. Deinum
1
如果您使用Hibernate,它也往往会在应用程序启动时占用大量时间。 - Knut Forkalsrud
Spring的自动绑定按类型与工厂bean相结合,一旦添加了大量bean和依赖项,就有可能变得很慢。 - Knut Forkalsrud
3
感谢所有的评论 - 很不幸我无法提供代码(有很多内部jar包),但是我仍在寻找调试的方法。是的,我可能正在使用A或B或正在执行X或Y,这会导致速度变慢。我如何判断哪个是问题所在?如果我添加一个依赖项X,它有15个传递依赖项,我怎么知道其中哪个使它变慢了呢?如果我可以找到原因,是否有什么后续措施可以让Spring停止检查它们?这样的指针将非常有用! - steady rain
根据您配置的数据源,初始化数据库架构也需要相当长的时间。 - Kevin Wittek
显示剩余3条评论
11个回答

91
其他回答没有深入探讨,也没有提供科学证据。Spring Boot团队经过一番努力为Boot 2.0缩短启动时间,票号11226包含了许多有用的信息。还有一个票号7939,旨在向条件评估添加计时信息,但似乎没有具体的预计完成时间。 调试Boot启动最有用、最系统的方法是由Dave Syer完成的。https://github.com/dsyer/spring-boot-startup-bench 我也有类似的用例,所以我采用了Dave的微基准测试方法,并进行了改进。结果就是boot-benchmark项目。我设计它可以用于测量任何Spring Boot应用程序的启动时间,使用bootJar(在Boot 1.5中称为bootRepackage)Gradle任务生成的可执行jar文件。欢迎使用并提供反馈。
我的发现如下:
  1. CPU很重要。
  2. 使用-Xverify:none启动JVM会有很大帮助。
  3. 排除不必要的自动配置也有帮助。
  4. Dave建议使用JVM参数-XX:TieredStopAtLevel=1,但我的测试结果并没有显示出明显的改进。此外,-XX:TieredStopAtLevel=1可能会减缓您的第一个请求。
  5. 报道称主机名解析较慢,但我在测试的应用程序中并未发现此问题。

根据我的Gradle包装器,我正在使用v4.6。如果你有更具体的问题,请创建一个gist并在此处发布链接,"无法构建"是一个非常模糊的陈述。您的gist应列出您遵循的步骤和您遇到的错误。 - Abhijit Sarkar
2
另外,您是否可以提供一个示例,说明有人如何使用您的基准测试与自定义应用程序?它是否必须像“minimal”一样作为项目添加,还是可以简单地提供jar文件?我尝试过前者,但没有进展。 - filpa
4
在生产环境中不要运行“-Xverify:none”,因为它会破坏代码验证并导致问题。如果您运行的应用程序仅运行几秒钟,那么“-XX:TieredStopAtLevel = 1”是可以接受的;否则,它将不太有效,因为它将向JVM提供长时间运行的优化。 - loicmathieu
2
许多连接池(肯定包括Oracle UCP,但在我的测试中也包括Hikari和Tomcat)会对池中的数据进行加密。实际上,我不知道它们是否加密了连接信息,或者是对流进行了封装。无论如何,加密使用随机数生成,因此具有高可用性、高吞吐量的熵源会对性能产生明显影响。 - Daniel
@WirJun 这个答案并不适用于 JDK 50 之前的版本;但在它被写出来时是正确的,现在依然如此。存在着 -Xverify:none 这种选项,以便那些愿意承担风险的人可以这么做;这取决于他们自己的抉择,而不是你我的。我的工作就是记录可用的选项。 - Abhijit Sarkar
显示剩余11条评论

80

Spring Boot 自动配置了很多可能不需要的内容。因此,您可能只想缩小适用于应用程序的自动配置范围。要查看包含在内的自动配置的完整列表,请以 DEBUG 模式运行 org.springframework.boot.autoconfigure 的日志记录 (logging.level.org.springframework.boot.autoconfigure=DEBUGapplication.properties 中)。另一个选项是使用 --debug 选项运行 Spring Boot 应用程序: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 {

这段代码是从这篇博客文章中复制而来的。


1
你有测量过吗??它快得多吗?在我看来,这是一个例外情况,更重要的是确保Spring测试上下文缓存正常工作。 - idmitriev
@idmitriev,我刚刚在我的应用程序上测量了一下,发现如果不排除自动配置类,我的应用程序启动需要53秒左右,而如果排除这些类,则只需73秒。尽管我排除的类比上面列出的还多。 - apkisbossin
3
如何处理私有配置类? - user1767316
1
有没有一种自动知道哪些自动配置实际被使用的方法?也许有一些长时间运行的东西,可以累加应用程序生命周期中使用的所有自动配置,然后您可以轮询执行器端点以查看该列表? - payne
@payne,我不知道你所描述的任何内容。 - luboskrnac

31

Spring Boot 2.2.M1 添加了懒加载特性。

默认情况下,当刷新应用程序上下文时,上下文中的每个bean都会被创建并注入其依赖项。相比之下,当一个bean定义被配置为延迟初始化时,它不会被创建,它的依赖关系也不会被注入,直到需要使用该bean时才进行。

启用懒加载 设置 spring.main.lazy-initializationtrue

何时启用懒加载

懒加载可以显著提高启动时间,但也有一些明显的缺点,因此启用它时需要小心谨慎。

更多详细信息,请查看文档

更新:

Spring Boot 2.4.0 - 启动端点

Spring Boot 2.4.0新增了一个Startup endpoint,可用于识别启动时间超出预期的bean。

您可以在此处获取有关应用程序启动跟踪的更多详细信息。


6
如果你启用了延迟初始化,第一次加载速度非常快,但当客户端首次访问时可能会注意到一些延迟。我强烈建议将其用于开发而非生产环境。 - Isuru Dewasurendra
2
正如@IsuruDewasurendra所建议的那样,这确实不是推荐的方式,它会在应用程序开始服务负载时显著增加延迟。 - Narendra Jaggi
1
它只是把问题拖延下去。 - Abhijit Sarkar
我只在开发阶段使用懒加载,因为第一次访问非常慢,但在Spring Boot中它是一个很好的特性。 - Caio
1
即使用户第一次访问速度较慢,这仍是启动不需要任何 bean 的初创企业的好方法。即使在生产环境中,与同一人每次等待启动时间相比,我认为这种方式非常可接受。 - Anddo

13

如在此问答中所述,我认为最好的方法是排除那些你知道不需要的依赖项,而非仅添加你认为需要的依赖项。

请参阅:最小化Spring Boot启动时间

总之:

您可以通过在命令行启动应用程序时指定--debug来查看底层发生的情况并启用调试日志记录。您还可以在application.properties中指定debug=true。

另外,您可以像这样简单设置application.properties中的日志级别:

logging.level.org.springframework.web: DEBUG logging.level.org.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 {
}

8

这里有一个完整的可能操作列表: https://spring.io/blog/2018/12/12/how-fast-is-spring

我会从Spring方面提取最重要的笔记(稍微进行了调整):

  • 从Spring Boot Web Starters中排除Classpath:
    • 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修复Spring Boot配置文件的位置(命令行参数或系统属性等)。 IDE测试示例:spring.config.location=file://./src/main/resources/application.properties
  • 使用spring.jmx.enabled=false关掉JMX(这是Spring Boot 2.2中的默认值),如果不需要的话
  • 默认情况下使bean定义懒加载。在Spring Boot 2.2中有一个新标记spring.main.lazy-initialization=true(在旧版Spring中使用LazyInitBeanFactoryPostProcessor)。
  • 解压缩fat jar并使用显式类路径运行。
  • 使用-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。希望能够帮到你!

2

警告: 如果您不使用Hibernate DDL进行自动DB模式生成,也不使用L2缓存,则本答案不适用于您。请滚动到下面。

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

application.yml:

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

Test results:

  1. L2 cache is ON and ddl-auto: update: 54 seconds

     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 cache is OFF and ddl-auto: none: 32 seconds

     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秒的时间! 现在我想知道我要用这么多自由时间做什么。

hibernate.hbm2ddl.auto=update与L2缓存无关。ddl..=update指定扫描当前数据库模式,并计算必要的SQL以更新模式以反映您的实体。'None'不执行此验证(也不尝试更新模式)。最佳实践是使用像Liquibase这样的工具,您将在其中处理模式更改,并且还可以跟踪它们。 - Radu Toader
@RaduToader 这个问题和我的回答都是关于加速Spring Boot启动时间的。它们与Hibernate DDL vs Liquibase讨论无关;这些工具都有其优缺点。我的观点是,我们可以禁用DB模式更新,并仅在必要时启用。即使模型自上次运行以来没有更改(用于将DB模式与自动生成的模式进行比较),Hibernate在启动时也需要显着的时间。对于L2缓存也是如此。 - naXa stands with Ukraine
是的,我知道这一点,但我的观点是不解释它的真正作用有点危险。你很容易会导致数据库为空。 - Radu Toader
@RaduToader,我的回答中有一个关于DB初始化的文档页面链接。你看过了吗?它包含了详尽的指南,列出了所有最流行的工具(包括Hibernate和Liquibase,以及JPA和Flyway)。今天我还在我的回答顶部添加了一个明确的警告。你认为我需要做其他的改动来解释后果吗? - naXa stands with Ukraine
完美。谢谢。 - Radu Toader

1

如果你想要优化手动测试的开发周转时间,我强烈推荐使用devtools

使用spring-boot-devtools的应用程序会在类路径上的文件更改时自动重启。

只需重新编译--服务器将自动重启(对于Groovy,您只需要更新源文件)。如果您使用的是IDE(例如“vscode”),它可能会自动编译您的Java文件,因此仅保存Java文件就可以间接地启动服务器重新启动,并且在这方面,Java与Groovy一样无缝。

这种方法的美妙之处在于增量重启会跳过一些从头开始的启动步骤,因此您的服务将更快地恢复运行!


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


1

查看Spring Boot启动报告

https://github.com/maciejwalkowiak/spring-boot-startup-report

Spring Boot Startup Report 库生成一个交互式的 Spring Boot 应用程序启动报告,让您了解是什么导致了应用程序启动时间,并帮助优化它。 启动报告在运行时作为交互式 HTML 页面提供 在集成测试中生成启动报告 火焰图 按类或注释搜索

特点

  • 启动报告在运行时作为交互式 HTML 页面提供
  • 在集成测试中生成启动报告
  • 火焰图
  • 按类或注释搜索

在我看来,这是非常酷的实用工具。


-3

我觉得奇怪为什么之前没有人建议过这些优化。以下是一些有关项目构建和启动优化的通用提示:

  • 从杀毒软件扫描中排除开发目录:
    • 项目目录
    • 生成输出目录(如果它不在项目目录内)
    • IDE索引目录(例如 ~/.IntelliJIdea2018.3)
    • 部署目录(Tomcat 中的 Web 应用程序)
  • 升级硬件。使用更快的 CPU 和 RAM,更好的互联网连接(用于下载依赖项)和数据库连接,切换至 SSD(今天 NVMe SSD 是性能最好的存储设备)。显卡并不重要。
  • 使用最新版本的 Gradle 和 JVM。来源:简单的性能提升
  • 并行执行。通过使用更多的并发进程,可以显著减少整个构建时间的并行构建。

警告

  1. 第一个选项的代价是降低安全性。
  2. 第二个选项需要花费金钱(显然)。

1
问题是关于提高启动时间,而不是编译时间。 - ArtOfWarfare
@ArtOfWarfare 请再次阅读问题。问题陈述了问题为“我不高兴它需要那么长时间,主要是因为它破坏了开发流程”。我认为这是一个主要问题,并在我的答案中进行了解决。 - naXa stands with Ukraine

-5
在我的情况下,断点太多了。当我点击“静音断点”并在调试模式下重新启动应用程序时,应用程序的启动速度快了10倍。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接