在本教程中,我们将介绍有助于减少 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命令上创建本机映像。