1. Introduction
1.绪论
In this tutorial, we’ll cover different configurations and setups that can help decrease Spring Boot startup time. First, we’ll go over Spring specific configurations. Second, we’ll cover Java virtual machine options. Finally, we’ll cover how we can leverage GraalVM and native image compilation to reduce startup time further.
在本教程中,我们将介绍不同的配置和设置,以帮助减少Spring Boot的启动时间。首先,我们将讨论Spring的具体配置。其次,我们将介绍Java虚拟机选项。最后,我们将介绍如何利用GraalVM和本地镜像编译来进一步减少启动时间。
2. Spring Tweaks
2.Spring调整
Before we start, let’s set up a test application. We’ll use Spring Boot version 2.5.4 with Spring Web, Spring Actuator, and Spring Security as dependencies. In pom.xml, we’ll add spring-boot-maven-plugin with configuration to pack our application in a jar file:
在我们开始之前,让我们建立一个测试应用程序。我们将使用Spring Boot 2.5.4版本,并将Spring Web、Spring Actuator和Spring Security作为依赖项。在pom.xml中,我们将添加spring-boot-maven-plugin,并配置将我们的应用打包成jar文件。
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<finalName>springStartupApp</finalName>
<mainClass>com.baeldung.springStart.SpringStartApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
We run our jar file with the standard java -jar command and monitor the start time of our application:
我们用标准的java -jar 命令运行我们的jar文件,并监测我们的应用程序的启动时间。
c.b.springStart.SpringStartApplication : Started SpringStartApplication in 3.403 seconds (JVM running for 3.961)
As we can see, our application starts at approximately 3.4 seconds. We’ll use this time as a reference for future tweaks.
我们可以看到,我们的应用程序在大约3.4秒时启动。我们将把这个时间作为未来调整的参考。
2.1. Lazy Initialization
2.1.懒惰的初始化
Spring Framework has support for lazy initialization. Lazy initialization means that Spring won’t create all beans on startup. Also, Spring will inject no dependencies until that bean is needed. Since Spring Boot version 2.2. it’s possible to enable lazy initialization using the application.properties:
懒惰初始化意味着Spring不会在启动时创建所有Bean。此外,Spring将不注入任何依赖关系,直到需要该Bean为止。自Spring Boot 2.2版以来,可以使用application.properties来启用懒惰初始化。
spring.main.lazy-initialization=true
After building a new jar file and starting it as in the previous example, the new startup time is slightly better:
在建立一个新的jar文件后,像前面的例子一样启动它,新的启动时间稍微好一些。
c.b.springStart.SpringStartApplication : Started SpringStartApplication in 2.95 seconds (JVM running for 3.497)
Depending on the size of our codebase, lazy initialization can result in a significant amount of startup time reduction. The reduction depends on the dependency graph of our application.
根据我们代码库的大小,懒惰初始化可以显著减少启动时间。减少的时间取决于我们应用程序的依赖图。
Also, lazy initialization has benefits during development while using DevTools hot restart functionality. An increased number of restarts with lazy initialization will enable JVM to optimize the code better.
另外,在开发过程中,在使用DevTools热重启功能时,懒惰初始化也有好处。使用懒惰初始化增加的重启次数将使JVM能够更好地优化代码。
However, lazy initialization has a few drawbacks. The most significant disadvantage is that the application will serve the first request slower. Because Spring needs time to initialize the required beans, another disadvantage is that we can miss some errors on startup. This can result in ClassNotFoundException during runtime.
然而,懒惰的初始化有一些缺点。最重要的缺点是,应用程序为第一个请求提供服务的速度会变慢。因为Spring需要时间来初始化所需的Bean,另一个缺点是我们可能在启动时错过一些错误。这可能导致在运行时出现ClassNotFoundException。
2.2. Excluding Unnecessary Autoconfiguration
2.2.排除不必要的自动配置
Spring Boot always favored convention over configuration. Spring may initialize beans that our application doesn’t require. We can check all autoconfigured beans using startup logs. Setting the logging level to DEBUG on org.springframework.boot.autoconfigure in the application.properties:
Spring Boot总是倾向于惯例而不是配置。Spring可能会初始化我们的应用程序不需要的Bean。我们可以使用启动日志检查所有自动配置的Bean。在application.properties中,将org.springframework.boot.autoconfigure的日志级别设为DEBUG。
logging.level.org.springframework.boot.autoconfigure=DEBUG
In the logs, we’ll see new lines dedicated to autoconfiguration, starting with:
在日志中,我们会看到专门用于自动配置的新行,开始是。
============================
CONDITIONS EVALUATION REPORT
============================
Using this report, we can exclude parts of the application’s configuration. To exclude part of the configuration, we use @EnableAutoConfiguration annotation:
使用这个报告,我们可以排除应用程序的部分配置。为了排除部分配置,我们使用@EnableAutoConfigurationannotation。
@EnableAutoConfiguration(exclude = {JacksonAutoConfiguration.class, JvmMetricsAutoConfiguration.class,
LogbackMetricsAutoConfiguration.class, MetricsAutoConfiguration.class})
If we excluded the Jackson JSON library and some of the metrics configuration that we don’t use, we could save some time on startup:
如果我们把Jackson JSON库和一些我们不使用的指标配置排除在外,我们可以在启动时节省一些时间。
c.b.springStart.SpringStartApplication : Started SpringStartApplication in 3.183 seconds (JVM running for 3.732)
2.3. Other Minor Tweaks
2.3.其他小调整
Spring Boot comes with an embedded servlet container. By default, we get Tomcat. While Tomcat is good enough in most cases, other servlet containers can be more performant. In tests, Undertow from JBoss performs better than Tomcat or Jetty. It requires less memory and has a better average response time. To switch to Undertow, we need to change pom.xml:
Spring Boot带有一个嵌入式的Servlet容器。默认情况下,我们得到了Tomcat。虽然Tomcat在大多数情况下已经足够好,但是其他的servlet容器可能会有更高的性能。在测试中,来自JBoss的Undertow表现得比Tomcat或Jetty更好。它需要的内存更少,而且平均响应时间更好。要切换到Undertow,我们需要改变pom.xml。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
The following minor improvement can be in the classpath scan. Spring classpath scanning is fast action. We can improve startup time by creating a static index when we have a large codebase. We need to add a dependency to the spring-context-indexer to generate the index. Spring doesn’t require any additional configuration. During compile-time, Spring will create an additional file in META-INF\spring.components. Spring will use it automatically during startup:
在classpath扫描中可以有以下小改进。Spring的classpath扫描是快速动作。当我们有一个大的代码库时,我们可以通过创建静态索引来改善启动时间。我们需要给spring-context-indexer添加一个依赖项来生成索引。Spring并不要求任何额外的配置。在编译时,Spring将在META-INF\spring.component中创建一个附加文件。Spring将在启动时自动使用它。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>${spring.version}</version>
<optional>true</optional>
</dependency>
Since we have only one Spring component, this tweak didn’t produce significant results in our tests.
由于我们只有一个Spring组件,这个调整在我们的测试中并没有产生明显的效果。
Next, there are several valid places for application.properties (or .yml) files. Most usual are at classpath root or in the same folder as the jar file. We can avoid searching multiple locations by setting an explicit path with spring.config.location parameter and save a couple of milliseconds on searching:
接下来,application.properties(或.yml)文件有几个有效位置。最常见的是在classpath根部或与jar文件在同一文件夹中。我们可以通过使用spring.config.location参数设置一个明确的路径来避免搜索多个位置,并在搜索时节省几毫秒时间。
java -jar .\target\springStartupApp.jar --spring.config.location=classpath:/application.properties
Finally, Spring Boot offers some MBeans to monitor our application using JMX. Turn off JMX entirely and avoid the cost of creating those beans:
最后,Spring Boot提供了一些MBeans,以便使用JMX监控我们的应用程序。完全关闭JMX,避免创建这些Bean的成本:。
spring.jmx.enabled=false
3. JVM Tweaks
3.JVM的调整
3.1. Verify Flag
3.1.Verify标志
This flag sets bytecode verifier mode. Bytecode verification provides whether classes are properly formated and within JVM specification constraints. We set this flag on JVM during startup.
该标志设置字节码验证器模式。字节码验证提供了类的格式是否正确,是否在JVM的规范约束之内。我们在启动时在JVM上设置这个标志。
There are a couple of options for this flag:
这面旗帜有几个选项。
- -Xverify is the default value and enables verification on all non-bootloader classes.
- -Xverify:all enables verification of all classes. This setup will have a significant negative performance impact on startups.
- -Xverify:none (or -Xnoverify). This option disables the verifier completely and will reduce startup time significantly.
We can pass this flag on startup:
我们可以在启动时传递这个标志:
java -jar -noverify .\target\springStartupApp.jar
We’ll get a warning from JVM that this option is deprecated. Also, startup time will decrease:
我们会从JVM那里得到一个警告,说这个选项已经过时了。同时,启动时间也会减少。
c.b.springStart.SpringStartApplication : Started SpringStartApplication in 3.193 seconds (JVM running for 3.686)
This flag brings in a significant tradeoff. Our application can break during runtime with an error that we could catch earlier. This is one of the reasons this option is marked as deprecated in Java 13. Hence it will be removed in future releases.
这个标志带来了一个重要的权衡。我们的应用程序在运行时可能会因为一个错误而中断,而我们可以早点抓住这个错误。这也是这个选项在Java 13中被标记为废弃的原因之一。 因此它将在未来的版本中被删除。
3.2. TieredCompilation Flag
3.2.TieredCompilation标志
Java 7 introduced tiered compilation. The HotSpot compiler will use different levels of compilation for the code.
Java 7引入了tiered compilation。HotSpot编译器将对代码使用不同级别的编译。
As we know, Java code first gets interpreted to bytecode. Next, bytecode gets compiled into machine code. This translation happens on the method level. C1 compiler compiles a method after a certain amount of calls. After even more runs C2 compiler compiles it increasing performance even more.
正如我们所知,Java代码首先被解释为字节码。接下来,字节码被编译成机器码。这种翻译发生在方法层面。C1编译器在调用一定数量的方法后进行编译。在更多的运行之后,C2编译器会编译它,从而进一步提高性能。
Using the -XX:-TieredCompilation flag, we can disable intermediate compilation tiers. This means that our methods will get interpreted or compiled with the C2 compiler for maximum optimization. This won’t result in a decrease in startup speed. What we need is to disable the C2 compilation. We can do this with -XX:TieredStopAtLevel=1 option. In conjunction with -noverify flag, this can reduce startup time. Unfortunately, this will slow down the JIT compiler at later stages.
使用-XX:-TieredCompilation标志,我们可以禁用中间编译层。这意味着我们的方法将被解释或用C2编译器编译,以实现最大的优化。这不会导致启动速度的降低。我们需要的是禁用C2编译。我们可以用-XX:TieredStopAtLevel=1选项来做到这一点。与-noverify标志相结合,这可以减少启动时间。不幸的是,这将在后期减慢JIT编译器的速度。
TieredCompilation flag alone brings in solid improvement:
仅仅是TieredCompilation标志就带来了坚实的改进。
c.b.springStart.SpringStartApplication : Started SpringStartApplication in 2.754 seconds (JVM running for 3.172)
For an extra kick, running both flags from this section in conjunction reduces startup time even more:
为了获得额外的刺激,将本节中的两个标志同时运行可以进一步减少启动时间。
java -jar -XX:TieredStopAtLevel=1 -noverify .\target\springStartupApp.jar
c.b.springStart.SpringStartApplication : Started SpringStartApplication in 2.537 seconds (JVM running for 2.912)
4. Spring Native
4.Spring的土著
Native image is Java code compiled using an ahead-of-time compiler and packed into an executable file. It doesn’t require Java to run. The resulting program is faster and less memory-dependent since there is no JVM overhead. The GraalVM project introduced native images and required build tools.
本地图像是使用超前编译器编译的Java代码,并打包成一个可执行文件。它不需要Java来运行。由于没有JVM的开销,由此产生的程序速度更快,对内存的依赖性更小。GraalVM项目引入了本地图像和所需的构建工具。
Spring Native is an experimental module that supports the native compilation of Spring applications using the GraalVM native-image compiler. The ahead-of-time compiler executes several tasks during build time that reduce startup time (static analysis, removal of unused code, creating fixed classpath, etc.). There are still some limitations to native images:
Spring Native是一个实验性模块,它支持使用GraalVM本地图像编译器对Spring应用程序进行本地编译。超前编译器在构建期间执行若干任务,以减少启动时间(静态分析、删除未使用的代码、创建固定的classpath等)。本机图像仍有一些限制。
- It doesn’t support all Java features
- Reflection requires a special configuration
- Lazy class loading is unavailable
- Windows compatibility is an issue.
To compile an application to a native image, we need to add the spring-aot and spring-aot-maven-plugin dependency to pom.xml. Maven will create the native image on the package command in the target folder.
要将应用程序编译成本地镜像,我们需要在pom.xml中添加spring-aot和spring-aot-maven-plugin依赖。Maven将在target文件夹下的package命令中创建本地镜像。
5. Conclusion
5.总结
In this article, we explored different ways to improve the startup time of Spring Boot applications. First, we covered various Spring related features that can help reduce startup time. Next, we showed JVM-specific options. Last, we introduced Spring Native and native image creation. As always, the code used in this article can be found over on GitHub.
在这篇文章中,我们探讨了改善Spring Boot应用程序启动时间的不同方法。首先,我们介绍了各种与Spring相关的功能,这些功能可以帮助减少启动时间。接下来,我们展示了针对JVM的选项。最后,我们介绍了Spring Native和本地镜像创建。一如既往,本文中使用的代码可以在GitHub上找到。