注:

GraalVM Native Image 入门

简介

此实验逐步引导您完成使用 GraalVM Native Image 构建云原生 Java 应用的过程。它的目标读者是具有 Java 知识的开发人员。

GraalVM Native Image 技术可提前将 Java 代码编译为自包含的可执行文件。只有应用程序运行时所需的代码才会添加到可执行文件中。

本机映像生成的可执行文件具有以下几个重要优点:

许多先进的微服务框架都支持使用 GraalVM Native Image 进行提前编译,包括 Micronaut、Spring、Helidon 和 Quarkus。

此外,还有适用于 Native Image 的 Maven 和 Gradle 插件,因此您可以轻松构建、测试并将 Java 应用程序作为可执行文件运行。

注意:Oracle Cloud Infrastructure (OCI) 无需额外付费即可提供 GraalVM 企业版。

估计的实验室时间:45 分钟

实验室目标

在此实验室中,您可以执行以下任务:

注意:如果在实验室中看到笔记本电脑图标,则需要执行一些操作,例如输入命令。留意

# This is where we you will need to do something

STEP 1:连接到远程主机并检查开发环境

您的开发环境由远程主机提供:具有 Oracle Linux 8、1 CPU 和 32GB 内存的 OCI 计算实例。
在远程主机准备就绪之前将显示 Luna Labs 桌面环境,这至多需要两分钟。

通过在 Luna Desktop 环境中运行设置脚本,可以连接到远程主机。可通过“资源”选项卡使用此脚本

  1. 在桌面中,双击 Luna-Lab.html 图标。此时将打开该页面,其中显示特定于您的实验室的 Oracle Cloud Infrastructure 身份证明和信息。

  2. 将显示资源选项卡。请注意,资源标题旁边的齿轮在计算实例预配到茶云时会旋转。

  3. 预配实例时,这至多需要 2 分钟,您将在资源选项卡上看到以下内容

    Luna 资源标签

  4. 从“资源”选项卡复制设置 VS 代码环境的配置脚本。单击查看详细信息链接可显示配置。复制此项,如下面的屏幕截图中所示。

    复制配置脚本

  5. 打开终端,如下面的屏幕截图中所示:

    打开终端

  6. 将配置代码粘贴到终端中,终端将打开 VS 代码。

    粘贴终端 1

    粘贴终端 2

结束了!祝贺您,您现在已成功连接到 Oracle Cloud 中的远程主机!

关于开发环境的说明

您将使用 GraalVM Enterprise 21 作为该实验的 Java 平台。GraalVM 是来自 Oracle 的高性能 JDK 分销商。它基于可信的安全 Oracle Java SE 构建。

您的开发环境预配置了 GraalVM 和本机操作所需的原生映像工具。

您可以通过在终端中运行这些命令轻松检查,可以在 VS Code Terminal > New Terminal 中创建终端:

java -version

native-image --version

步骤 2:构建和运行演示应用程序

我们将使用演示应用程序来展示 GraalVM Native Image:命令行 Java 应用程序,用于计算当前目录及其子目录中的文件数。另外,应用程序还会计算文件的总大小。

远程主机中提供了该应用程序的源代码。

演示应用程序的注释

应用程序由两个 Java 文件组成,这些文件位于 src 目录中:

应用程序可以手动构建,也可以使用 Maven 配置文件构建。Maven 构建配置由 pom.xml 文件提供。Maven 配置文件是一种在单个 pom.xml 文件中具有不同构建配置的好方法。您可以在此处找到有关 Maven 配置文件的更多信息。

您可以浏览打开的 VS 代码中的文件。

此实验室中将使用多个配置文件,每个配置文件都具有特定用途:

  1. native:此配置文件使用 GraalVM Native Image 构建可执行文件。
  2. java_agent:此配置文件使用跟踪代理构建 Java 应用程序,该代理跟踪应用程序中动态代码的所有使用情况,并将此信息捕获到配置文件中。稍后详述。

可以使用特定的 Maven 配置文件,将其作为参数传递给 mvn 命令。配置文件的名称附加到 -P 标志后面。可以在 VS 代码内的终端内运行以下命令。

以下示例说明在使用 Maven 构建时如何调用 native 配置文件:

mvn clean package -Pnative

现在,您可以基本了解应用程序运行什么,以查看其运行方式。

  1. 构建项目,然后从我们在 VS Code 中打开的终端中运行该项目:

    mvn clean package exec:exec
    

    以上命令可执行以下操作:

    1. 清除项目以删除任何已生成或已编译的构件。
    2. 创建包含应用程序的可运行 JAR 文件。此 JAR 文件稍后将由 Native Image 使用。
    3. 通过运行 exec 插件运行应用程序。

    您应看到生成的输出中包含以下内容(应用程序报告的文件数可能有所不同):

    Counting directory: .
    Total: 15 files, total size = 511.9 KiB
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    

STEP 3:将 Java 应用程序转换为可执行文件

接下来,您将使用 GraalVM Native Image 构建应用的可执行版本。作为快速提醒,GraalVM Native Image 是一种提前编译技术,可将您的 Java 应用转换为自包含的可执行文件,不需要 JDK 运行即可快速启动且高效。

远程主机上预安装了 GraalVM Native Image。

  1. 首先,检查 target 目录中是否有已编译的 JAR 文件:

    ls ./target
    drwxrwxr-x 1 krf krf    4096 Mar  4 11:12 archive-tmp
    drwxrwxr-x 1 krf krf    4096 Mar  4 11:12 classes
    drwxrwxr-x 1 krf krf    4096 Mar  4 11:12 generated-sources
    -rw-rw-r-- 1 krf krf  496273 Mar  4 11:38 graalvmnidemos-1.0-SNAPSHOT-jar-with-dependencies.jar
    -rw-rw-r-- 1 krf krf    7894 Mar  4 11:38 graalvmnidemos-1.0-SNAPSHOT.jar
    drwxrwxr-x 1 krf krf    4096 Mar  4 11:12 maven-archiver
    drwxrwxr-x 1 krf krf    4096 Mar  4 11:12 maven-status
    

    您需要的文件是 graalvmnidemos-1.0-SNAPSHOT-jar-with-dependencies.jar

  2. 从命令行生成可执行文件。您无需使用 Maven 插件即可使用 GraalVM Native Image,但它可以提供帮助。从项目的根目录 demo 运行以下命令:

    native-image -jar ./target/graalvmnidemos-1.0-SNAPSHOT-jar-with-dependencies.jar --no-fallback -H:Class=oracle.App -H:Name=file-count
    

    这将在当前目录中生成名为 file-count 的可执行文件。

  3. 按如下方式运行此可执行文件:

    ./file-count
    
  4. 现在应用程序的时间。首先将其作为可执行文件运行,然后使用常规 java 命令运行:

    time ./file-count
    

    time java -cp ./target/graalvmnidemos-1.0-SNAPSHOT-jar-with-dependencies.jar oracle.App
    

    native-image 命令生成的可执行文件运行速度比相应的 Java 应用程序快得多。

让我们更深入地探讨如何创建可执行文件。

在步骤 2 中传递给 native-image 命令的参数指定了什么?

可以在此处找到完整的文档。

您还可以使用 GraalVM Native Image Maven 插件运行 native-image 工具。项目 pom.xml(Maven 配置)文件包含以下代码片段,说明了如何使用插件构建可执行文件:

<!-- Native Image -->
<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
    <version>${native.maven.plugin.version}</version>
    <extensions>true</extensions>
    <executions>
    <execution>
        <id>build-native</id>
        <goals>
        <goal>build</goal>
        </goals>
        <phase>package</phase>
    </execution>
    </executions>
    <configuration>
        <skip>false</skip>
        <imageName>${exe.file.name}</imageName>
        <mainClass>${app.main.class}</mainClass>
        <buildArgs>
            <buildArg>--no-fallback</buildArg>
            <buildArg>--report-unsupported-elements-at-runtime</buildArg>
        </buildArgs>
    </configuration>
</plugin>

本机映像 Maven 插件执行生成可执行文件的繁重工作。可以使用 <skip>true</skip> 标记禁用它。另请注意,您可以通过 <buildArgs/> 标记将参数传递给 native-image

有关 GraalVM Native Image 插件的完整文档位于此处

要使用 Maven 配置文件构建可执行文件,请运行:

mvn clean package -Pnative

Maven 构建会将可执行文件 file-count 放置到 target 目录中。

可以按如下方式运行可执行文件:

./target/file-count

STEP 4:使用反射 - 向 Log4J 添加依赖关系

在此步骤中,您将构建可执行文件,该文件与 Java 的动态功能配合使用。

说要向应用程序添加一个依赖于反射的库或一些代码。测试反射的一个好候选对象是 Log4J 日志记录框架。它已作为依赖项添加到项目 pom.xml 文件中:

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

要更改应用程序以便使用 log4j,请编辑 ListDir.java 文件并取消注释几行。

  1. 使用 VS 代码打开 ListDir.java 文件

  2. 对声明 log4j 导入的行取消注释,然后取消对以下行的注释:

    //import org.apache.log4j.Logger;
    

    //final static Logger logger = Logger.getLogger(ListDir.class);
    

    /*
    // Add some logging
    if(logger.isDebugEnabled()){
        logger.debug("Processing : " + dirName);
    }
    */
    

    /*
    // Add some logging
    if(logger.isDebugEnabled()){
        logger.debug("Processing : " + f.getAbsolutePath());
    }
    */
    
  3. 保存文件

    现在已将日志记录添加到应用程序中,您可以通过重建和运行来查看更改结果。

    mvn clean package exec:exec
    

    您应该看到与之前看到的输出类型相同,但添加了更多日志记录。

  4. 接下来,使用 Maven 配置文件构建可执行文件:

    mvn clean package -Pnative
    
  5. 运行生成的可执行文件,该文件现在包含日志记录:

    ./target/file-count
    

    这会产生错误:

    Exception in thread "main" java.lang.NoClassDefFoundError
            at org.apache.log4j.Category.class$(Category.java:118)
            at org.apache.log4j.Category.<clinit>(Category.java:118)
            at java.lang.Class.ensureInitialized(DynamicHub.java:552)
            at oracle.ListDir.<clinit>(ListDir.java:75)
            at oracle.App.main(App.java:63)
    Caused by: java.lang.ClassNotFoundException: org.apache.log4j.Category
            at java.lang.Class.forName(DynamicHub.java:1433)
            at java.lang.Class.forName(DynamicHub.java:1408)
            ... 5 more
    

    这里怎么了?

使用 Java 的动态功能

此异常是由添加 Log4J 库导致的,因为它依赖于反射。
native-image 工具执行积极静态分析,以查看应用程序中使用的类。对于未使用的任何类,工具将假定不需要这些类。这称为“封闭式全局”假设 - 生成可执行文件时必须知道需要装入的所有内容。如果静态分析无法找到它,则它不会包含在可执行文件中。

反射是 Java 的核心功能,因此如何使用反射以及利用 GraalVM Native Image 提供的速度提升?您需要一种方法来让 native-image 工具了解反射的任何用法。

幸运的是,native-image 工具能够在配置文件中读取,这些配置文件指定通过反射引用的所有类。

您可以手动执行此操作,或者 GraalVM Java 运行时附带的 Java 跟踪代理可以为您完成此操作。代理生成的 JSON 文件记录应用程序运行时可以找到的所有反射实例、JNI 实例、代理和资源访问。

注意:运行跟踪代理时,务必在应用程序中执行所有代码路径,以确保识别所有的反射情况。

有关跟踪代理的完整文档,请参见此处

STEP 5:使用跟踪代理

现在,在运行应用程序时,使用跟踪代理生成反射配置。

  1. 使用跟踪代理运行应用程序:

    java -agentlib:native-image-agent=config-output-dir=./src/main/resources/META-INF/native-image -cp ./target/graalvmnidemos-1.0-SNAPSHOT-jar-with-dependencies.jar oracle.App
    

    查看跟踪代理创建的配置文件:

    ls -l src/main/resources/META-INF/native-image/
    

    在生成的输出中应看到以下内容:

    total 56
    -rw-r--r--  1 kfoster  staff     4B Dec  2 19:13 jni-config.json
    -rw-r--r--  1 kfoster  staff    86B Nov  9 20:46 native-image.properties
    -rw-r--r--  1 kfoster  staff    65B Dec  2 19:13 predefined-classes-config.json
    -rw-r--r--  1 kfoster  staff     4B Dec  2 19:13 proxy-config.json
    -rw-r--r--  1 kfoster  staff   521B Dec  2 19:13 reflect-config.json
    -rw-r--r--  1 kfoster  staff   101B Dec  2 19:13 resource-config.json
    -rw-r--r--  1 kfoster  staff     4B Dec  2 19:13 serialization-config.json
    

    注意:项目包含可为您执行此操作的 Maven 配置文件。运行以下命令以使用跟踪代理:

    mvn clean package exec:exec -Pjava_agent
    
  2. 现在重新生成可执行文件。这次将应用跟踪代理生成的配置文件:

    mvn package -Pnative
    
  3. 最后,执行生成的文件:

    time ./target/file-count
    

    可执行文件可以按预期方式工作并生成输出中的日志消息。

这起作用是因为跟踪代理生成的文件记录了反射引用的类。native-image 工具现在知道它们在应用程序中使用,因此不会将其从生成的可执行文件中排除。

有关 -agentlib 参数的位置的说明

请注意,代理参数必须出现在任何 -jar-classpath 参数之前。还应指定要将文件写入到的目录。建议的位置位于 src/main/resources/META-INF/native-image 下。native-image 工具自动选取放置在此位置的文件。

有关配置生成可执行文件的说明

您还可以使用 Java 属性文件(默认情况下为 src/main/resources/META-INF/native-image/native-image.properties)将参数传递给 native-image 工具。demo 目录中有一个示例文件,可以让您了解如何处理该文件。

结论

在此实验室中,您试用了一些 GraalVM Native Image 功能:

  1. 如何从 Java 命令行应用程序生成快速可执行文件
  2. 如何使用 Maven 构建可执行文件
  3. 如何使用跟踪代理自动执行跟踪和记录反射过程

使用 GraalVM Native Image 编写高效、安全且可立即扩展的云原生 Java 应用!

了解更多

更多学习资源

docs.oracle.com/learn 上浏览其他实验室,或者在 Oracle Learning YouTube 渠道上访问更多免费学习内容。此外,访问 education.oracle.com/learning-explorer 以成为 Oracle Learning Explorer。

有关产品文档,请访问 Oracle 帮助中心