如何为 Spring Boot 应用程序构建 GraalVM 映像

在本文中,您将学习如何为 Spring Boot 应用程序构建 GraalVM 映像。按照这些实际步骤,您将能够将它们应用于您自己的 Spring 引导应用程序。享受!

介绍

Java 是一种伟大的编程语言,并且独立于平台。一次写,随处运行!但这是有代价的。Java 是可移植的,因为 Java 将你的代码编译为字节码。字节码是计算机目标代码,解释器(阅读:虚拟机)可以解释并转换为机器代码。启动 Java 应用程序时,虚拟机会将字节码转换为特定于平台的字节码,称为本机机器码。这是由实时编译器 (JIT) 完成的。如您所知,此转换在启动过程中需要一些时间。

假设您有一个用例,其中快速启动时间非常重要。一个例子是用 Java 编写的 AWS Lambda。AWS Lambda 在没有应用程序活动时不会运行。当请求需要 AWS Lambda 运行时,Lambda 需要非常快速地启动、执行,然后再次关闭。每次 Lambda 启动时,JIT 编译器都需要完成其工作。在此用例中,JIT 编译会占用不必要的时间,因为您已经知道正在运行哪个平台。这就是提前编译 (AOT) 可以提供帮助的地方。使用 AOT,您可以为目标平台创建可执行文件或“本机映像”。您不再需要 JVM,也不需要 JIT 编译。这样可以缩短启动时间、减少内存占用和降低 CPU 使用率。

GraalVM 可以将您的 Java 应用程序编译为本机映像。Spring Boot 有一个名为 Spring Native 的实验项目,它帮助 Spring Boot 开发人员创建原生映像。从 Spring Boot 3 开始,Spring Native 是 Spring Boot 的一部分,并且退出了实验阶段。

在本文的其余部分中,您将创建一个基本的 Spring 引导应用程序并为其创建 GraalVM 映像。

如果您想以互动方式了解有关 GraalVM 的更多信息,强烈建议您参加 GraalVM 研讨会。

本文中使用的来源可在 GitHub 上找到。

先决条件

本文的先决条件是:

  • 乌班图 22.04
  • 基本的 Linux 知识。
  • 基本的 Java 和 Spring Boot 知识。
  • SDKMAN 用于在 JDK 之间切换。

示例应用程序

首先要做的是创建一个示例应用程序。浏览到 Spring Initializr 并添加依赖项、Spring Web 和 GraalVM 本机支持。确保使用 Spring Boot 3,生成项目,并在您喜欢的 IDE 中打开它。

添加一个带有一个返回 hello 消息的终结点:HelloController

@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String hello() {
        return "Hello GraalVM!";
    }
}

构建应用程序:

$ mvn clean verify
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  6.971 s
[INFO] Finished at: 2023-02-18T10:26:33+01:00
[INFO] ------------------------------------------------------------------------

正如您在输出中看到的,构建 Spring Boot 应用程序大约需要 7 秒钟。

目标目录包含 jar 文件,大小约为 17.6MB。mygraalvmplanet-0.0.1-SNAPSHOT.jar

从存储库的根目录启动应用程序:

$ java -jar target/mygraalvmplanet-0.0.1-SNAPSHOT.jar
2023-02-18T10:30:15.013+01:00  INFO 17233 --- [           main] c.m.m.MyGraalVmPlanetApplication         : Starting MyGraalVmPlanetApplication v0.0.1-SNAPSHOT using Java 17.0.6 with PID 17233 (/home//mygraalvmplanet/target/mygraalvmplanet-0.0.1-SNAPSHOT.jar started by  in /home//mygraalvmplanet)
...
2023-02-18T10:30:16.486+01:00  INFO 17233 --- [           main] c.m.m.MyGraalVmPlanetApplication         : Started MyGraalVmPlanetApplication in 1.848 seconds (process running for 2.212)

正如您在输出中看到的,启动 Spring Boot 应用程序需要 1.848 秒。

借助 和记录在输出第一行中的 PID,您可以检查 CPU 和内存消耗:top

$ top -p 17233

输出显示消耗了 0.3% 的 CPU 和 0.6% 的内存。

创建本机映像

在上一节中,您像往常一样创建并运行了一个 Spring 引导应用程序。在本节中,您将创建 Spring 引导应用程序的本机映像,并将其作为可执行文件运行。

由于您在创建 Spring 引导应用程序时添加了 GraalVM 本机支持依赖项,因此以下代码片段将添加到 pom 文件中:


  
    
      org.graalvm.buildtools
      native-maven-plugin
    
    ...
  


借助 ,您可以使用 Maven 配置文件编译本机映像:native-maven-pluginnative

$ mvn -Pnative native:compile
...
[INFO] --- native-maven-plugin:0.9.19:compile (default-cli) @ mygraalvmplanet ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  8.531 s
[INFO] Finished at: 2023-02-05T16:50:20+01:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.graalvm.buildtools:native-maven-plugin:0.9.19:compile (default-cli) on project mygraalvmplanet: 'gu' tool wasn't found. This probably means that JDK at isn't a GraalVM distribution. -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] 

编译失败,因为 GraalVM 未用于编译。让我们先安装 GraalVM。

使用 SDKMAN 时,在 JDK 之间进行安装和切换相当简单。如果您对 SDKMAN 没有任何了解,请查看上一篇文章。

安装 GraalVM:

$ sdk install java 22.3.r17-nik

在要编译的终端中使用 GraalVM:

$ sdk use java 22.3.r17-nik

再次运行本机构建:

$ mvn -Pnative native:compile
...
Produced artifacts:
 /home//mygraalvmplanet/target/mygraalvmplanet (executable)
 /home//mygraalvmplanet/target/mygraalvmplanet.build_artifacts.txt (txt)
========================================================================================================================
Finished generating 'mygraalvmplanet' in 2m 15s.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  02:27 min
[INFO] Finished at: 2023-02-18T10:48:40+01:00
[INFO] ------------------------------------------------------------------------

构建现在大约需要 2-5 分钟。请记住,没有本机编译的构建大约需要七秒钟。这是构建时间的巨大增加。这是由于 AOT 编译。

目标目录包含一个可执行文件,其大小约为 66.2MB。与 jar 文件相比,这也是大小的增加,jar 文件的大小为 17.6MB。但请记住,可执行文件不需要 JVM 来运行,jar 文件需要。mygraalvmplanet

从存储库的根目录启动 Spring 引导应用程序:

$ target/mygraalvmplanet
2023-02-18T10:52:29.865+01:00  INFO 18085 --- [           main] c.m.m.MyGraalVmPlanetApplication         : Starting AOT-processed MyGraalVmPlanetApplication using Java 17.0.5 with PID 18085 (/home//mygraalvmplanet/target/mygraalvmplanet started by  in /home//mygraalvmplanet)
...
2023-02-18T10:52:29.920+01:00  INFO 18085 --- [           main] c.m.m.MyGraalVmPlanetApplication         : Started MyGraalVmPlanetApplication in 0.069 seconds (process running for 0.085)

如果您眨眼,您可能根本没有看到它开始,因为启动时间现在是 0.069 秒。与没有本机编译的 1.848 秒相比,这几乎快了 27 倍。

当您使用 top 查看 CPU 和内存消耗时,您会注意到 CPU 消耗可以忽略不计,内存消耗现在是可用内存的 0.2%,因此内存消耗降低了 3 倍。

注意:它现在是特定目标平台的可执行文件。

关于反思的事情

GraalVM 在编译类期间使用静态分析。仅分析应用程序中使用的类。这意味着在使用反射时可能会出现问题。Spring 在他们的代码中广泛使用了 Reflection,这也是 Spring Native 项目的原因之一。许多反射已从春季中删除。除此之外,当 GraalVM 在静态分析期间找不到类时,还可以指示 GraalVM 通过元数据文件添加类。您可以对自己的应用程序执行此操作,但对正在使用的依赖项没有任何影响。您可以要求维护者添加 GraalVM 元数据文件,但他们没有义务这样做。为了规避这个问题并使 Spring 开发人员的生活更加轻松,Spring 为 GraalVM Reachability 元数据存储库做出了贡献,并且在 Spring Boot 应用程序的本机编译期间会参考该存储库。

你可能感兴趣的:(java,jvm,spring)