1. 概述

Spring 6 引入了一个旨在优化应用性能的新特性:预先编译(Ahead-of-Time, AOT)支持。本文将探讨Spring 6的AOT优化功能如何工作、其优势以及如何使用它。

2. 预先编译

2.1. 即时编译器(JIT)

对于大多数使用的Java虚拟机(JVM),如Oracle的HotSpot JVM和OpenJDK,当编译源代码(.java文件)时,产生的字节码会被存储在.class文件中。这样,JVM会使用即时编译器将字节码转换为机器代码。

此外,即时编译涉及JVM在运行时对字节码的解释,并将经常执行的代码动态编译为原生机器代码。

2.2. 预先编译器(AOT)

预先编译(AOT)是一种在应用程序运行前将字节码编译为原生机器代码的技术。

Java虚拟机通常不支持此功能。然而,Oracle在OpenJDK项目中的HotSpot JVM实验性地发布了名为“GraalVM Native Image”的AOT功能,允许预先编译代码。

预编译后,计算机处理器可以直接执行代码,无需JVM解释字节码,从而提高了启动时间。

在这篇文章中,我们将不会详细讨论AOT编译器。有关AOT编译的详细介绍,请参考我们的另一篇文章:预先编译(AOT)概述

3. Spring 6中的AOT

3.1. AOT优化

构建Spring 6应用时,我们需要考虑三种不同的运行时选项:

  • 在JRE上运行的传统Spring应用。
  • 在编译阶段AOT阶段生成的代码,在JRE上运行。
  • 在编译阶段AOT阶段生成的代码,在GraalVM原生映像中运行。

让我们来看看Spring 6的新功能——第二种选择(第一种是传统的构建,另一种是原生映像)。

首先,我们需要设置环境以支持AOT编译:获取GraalVM的最新入门指南

通过AOT编译构建应用在性能和资源消耗方面具有多个优势:

  • 死代码消除:AOT编译器可以移除在运行时从未执行过的代码,这可以通过减少需要执行的代码量来提高性能。
  • 内联:内联是一种技术,AOT编译器将函数调用替换为实际的函数代码。这可以减少函数调用的开销,从而提高性能。
  • 常量传播:AOT编译器通过在编译时替换可确定的常量值来优化性能,消除了运行时计算的需求,从而提高性能。
  • 跨过程优化:AOT编译器可以通过分析程序的调用图来优化跨多个函数的代码,减少函数调用的开销并识别常见的子表达式,从而提高性能。
  • 对象定义:Spring 6的AOT编译器通过减少不必要的BeanDefinition实例来提高应用效率。

现在,我们使用命令构建带有AOT优化的应用:

mvn clean compile spring-boot:process-aot package

然后,我们使用以下命令运行应用:

java -Dspring.aot.enabled=true -jar <jar-name>

我们可以设置构建插件默认启用AOT编译:

<plugin>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-maven-plugin</artifactId>
     <executions>
     <execution>
         <id>process-aot</id>
         <goals>
         <goal>process-aot</goal>
         </goals>
     </execution>
     </executions>
</plugin>

3.2. AOT优化中的问题

当我们决定使用AOT编译构建应用时,可能会遇到一些问题,例如:

  • 反射:它允许在编译时无法确定的动态调用方法和访问字段。AOT编译器无法确定动态调用的类和方法。
  • 配置文件:配置文件的内容可能在运行时更改。AOT编译器无法动态确定使用的配置文件内容。
  • 代理:代理通过提供对象的代理或占位符来控制对另一个对象的访问。由于代理可以动态重定向方法调用到其他对象,这使得AOT编译器难以确定运行时将调用哪些类和方法。
  • 序列化:序列化将对象的状态转换为字节流,反之亦然。总体而言,这可能使AOT编译器难以确定运行时将调用哪些类和方法。

为了确定Spring应用中的哪些类存在问题,我们可以使用提供反射操作信息的代理。

现在,让我们配置Maven插件以包含一个JVM参数来协助解决这个问题:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <jvmArguments>
            -agentlib:native-image-agent=config-output-dir=target/native-image
        </jvmArguments>
    </configuration>
    <!- ... -->
</plugin>

然后,我们使用以下命令运行它:

./mvnw -DskipTests clean package spring-boot:run

target/native-image/目录下,我们会找到像reflect-config.jsonresource-config.json等生成的文件。

如果这些文件中定义了内容,那么是时候定义RuntimeHints,以便正确编译可执行文件。

4. 总结

本文介绍了Spring 6的AOT优化新特性。

如往常一样,示例的完整源代码可以在GitHub上找到。