加快Spring Boot启动的几种方法 | baeldung


在本教程中,我们将介绍有助于减少 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:
<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>

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

 
搜索多个位置
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命令上创建本机映像。