在本教程中,我们将介绍有助于减少 Spring Boot 启动时间的不同配置和设置:

  • 首先,我们将讨论 Spring 特定的配置。

  • 其次,我们将介绍 Java 虚拟机选项。

  • 最后,我们将介绍如何利用 GraalVM 和本机镜像编译来进一步缩短启动时间。

延迟初始化

Spring Framework 支持延迟初始化。延迟初始化意味着 Spring 不会在启动时创建所有 bean。此外,在需要该 bean 之前,Spring 不会注入任何依赖项。从 Spring Boot 2.2 版开始。可以使用application.properties启用延迟初始化:

spring.main.lazy-initialization=true

根据我们代码库的大小情况,延迟初始化甚至会导致很大的启动时间减少。这种减少取决于我们应用程序的依赖关系图。

此外,延迟初始化在使用 DevTools 热重启功能的开发过程中也有好处。使用延迟初始化增加重启次数将使 JVM 能够更好地优化代码。

但是,延迟初始化有一些缺点。最显着的缺点是应用程序会较慢地处理第一个请求。因为 Spring 需要时间来初始化所需的 bean,另一个缺点是我们可能会在启动时遗漏一些错误。这可能会在运行时导致ClassNotFoundException 。

排除不必要的自动配置

Spring Boot 总是喜欢约定而不是配置。Spring 可能会初始化我们的应用程序不需要的 bean。我们可以使用启动日志检查所有自动配置的 bean。在application.properties 中的org.springframework.boot.autoconfigure上将日志记录级别设置为 DEBUG : logging.level.org.springframework.boot.autoconfigure=DEBUG 在日志中,我们将看到专用于自动配置的新行,然后根据这些输出,我们可以排除这些应用程序配置。 为了排除部分配置,我们使用@EnableAutoConfiguration注解:

@EnableAutoConfiguration(exclude = {JacksonAutoConfiguration.class, JvmMetricsAutoConfiguration.class, LogbackMetricsAutoConfiguration.class, MetricsAutoConfiguration.class})

如果我们排除 Jackson JSON 库和一些我们不使用的指标配置,我们可以在启动时节省一些时间。

切换到 Undertow

Spring Boot 带有一个嵌入式 servlet 容器。默认情况下,我们得到 Tomcat。虽然 Tomcat 在大多数情况下已经足够好,但其他 servlet 容器的性能可能更高。在测试中,来自 JBoss 的 Undertow 的性能优于 Tomcat 或 Jetty。它需要更少的内存并具有更好的平均响应时间。要切换到 Undertow,我们需要更改pom.xml:

org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-tomcat org.springframework.boot spring-boot-starter-undertow

生成索引

Spring 类路径扫描是快速操作。当我们拥有大型代码库时,我们可以通过创建静态索引来缩短启动时间。我们需要给spring-context-indexer添加一个依赖来生成索引。Spring 不需要任何额外的配置。在编译时,Spring 将在META-INF\spring.components 中创建一个附加文件。Spring 会在启动时自动使用它:

org.springframework spring-context-indexer ${spring.version} true

搜索多个位置

application.properties(或 .yml)文件有几个有效的位置。最常见的是在类路径根目录或与 jar 文件相同的文件夹中。我们可以通过使用spring.config.location参数设置显式路径来避免搜索多个位置,并在搜索时节省几毫秒:

java -jar .\target\springStartupApp.jar –spring.config.location=classpath:/application.properties

最后,Spring Boot 提供了一些 MBean 来使用 JMX 监控我们的应用程序。完全关闭 JMX 并避免创建这些 bean 的成本: spring.jmx.enabled=false

JVM的Verify调整

此标志设置字节码验证器模式。字节码验证提供类的格式是否正确以及是否在 JVM 规范约束内。我们在启动期间在 JVM 上设置了这个标志。 Verify标志有几个选项:

  • -Xverify是默认值并启用对所有非引导加载程序类的验证。

  • -Xverify:all启用对所有类的验证。此设置将对初创公司产生显着的负面性能影响。

  • -Xverify:none(或-Xnoverify)。此选项将完全禁用验证程序并将显着减少启动时间。

我们可以在启动时传递这个标志:

java -jar -noverify .\target\springStartupApp.jar

我们将收到来自 JVM 的警告,指出该选项已被弃用。但是启动时间减少了。 这个标志带来了重要的权衡。我们的应用程序可能会在运行时因我们可以更早捕获的错误而中断。这是该选项在 Java 13 中被标记为已弃用的原因之一。因此它将在未来版本中删除。

JVM分层编译标志

Java 7 引入了分层编译。HotSpot 编译器将对代码使用不同级别的编译。

众所周知,Java 代码首先被解释为字节码。接下来,字节码被编译成机器码。这种转换发生在方法级别。C1 编译器在一定数量的调用后编译一个方法。运行更多次之后,C2 编译器会编译它,从而进一步提高性能。

使用-XX:-TieredCompilation标志,我们可以禁用中间编译层。这意味着我们的方法将使用 C2 编译器进行解释或编译,以实现最大程度的优化。这不会导致启动速度下降。我们需要的是禁用 C2 编译。我们可以使用-XX:TieredStopAtLevel=1选项来做到这一点。结合-noverify标志,这可以减少启动时间。不幸的是,这会在后期减慢 JIT 编译器的速度。

Spring Native

本机映像/镜像(Image)是使用提前编译器编译并打包成可执行文件的 Java 代码。它不需要Java来运行。由于没有 JVM 开销,因此生成的程序速度更快,对内存的依赖更少。该GraalVM项目介绍本机映像和所需的构建工具。

Spring Native是一个实验性模块,支持使用 GraalVM 原生镜像编译器对 Spring 应用程序进行原生编译。提前编译器在构建期间执行多项任务以减少启动时间(静态分析、删除未使用的代码、创建固定类路径等)。原生镜像仍然有一些限制:

  • 它不支持所有 Java 功能

  • 反射需要特殊的配置

  • 懒加载类不可用

  • Windows 兼容性是一个问题。

要将应用程序编译为原生映像,我们需要将spring-aot 和spring-aot-maven-plugin依赖项添加到pom.xml。Maven 将在目标文件夹中的package命令上创建本机映像。