写点什么

在 spring boot3 中使用 native image

作者:程序那些事
  • 2023-01-30
    广东
  • 本文字数:6854 字

    阅读完需:约 22 分钟

简介

在之前 spring boot3 文章中我们介绍了,spring boot3 的一个重要特性就是支持把 spring boot3 的应用编译成为 GraalVM 的 Native Image。


今天我们用具体的例子来给大家演示一下如何正确的将 spring boot3 的应用编译成为 native image。

安装 GraalVM

如果要把 spring boot3 的 app 编译成为 native 应用,需要 GraalVM 的支持。


什么是 GraalVM 呢?


从名字就可以看出来 GraalVM 是一个虚拟机,它的主要目标就是提升 java 应用程序的性能,并且消耗更少的资源。


它在 java HotSpot JVM 的基础上添加了 JIT 编译器和 AOT 来实现将应用编译成为本地可执行文件。除了 java 之外,GraalVM 还支持 JavaScript、Ruby、Python 等多种编程语言。


所以,为什么要用 GraalVM 呢?一个字:快。


安装 GraalVM 也比较简单,我们进入它的官方下载页面下载对应的版本即可:https://www.oracle.com/downloads/graalvm-downloads.html


GraalVM 跟 JDK 一样也有两个版本,社区版和企业版本,大家可以根据需要自行选择。


要注意的是 spring boot3 需要 GraalVM 22.3 以上的版本支持,大家可不要下载错了。


下载完成之后,我们可以像正常安装 JDK 一样来安装 GraalVM,这里以 mac 为例,假如我们安装的目录是/Library/Java/JavaVirtualMachines/graalvm-ee-java17-22.3.0,那么我们需要配置对应的 JAVA_HOME 和 PATH 环境变量如下:


 export PATH=/Library/Java/JavaVirtualMachines/graalvm-ee-java17-22.3.0/Contents/Home/bin:$PATH
export JAVA_HOME=/Library/Java/JavaVirtualMachines/graalvm-ee-java17-22.3.0/Contents/Home
复制代码


PATH 中有一个非常重要的命令叫做 gu,如果不添加 PATH,那么在使用中就可能遇到下面的异常:


'gu' tool wasn't found. This probably means that JDK at isn't a GraalVM distribution.
复制代码


安装完毕之后可以通过下面的命令来进行验证:


java -versionjava version "17.0.5" 2022-10-18 LTSJava(TM) SE Runtime Environment GraalVM EE 22.3.0 (build 17.0.5+9-LTS-jvmci-22.3-b07)Java HotSpot(TM) 64-Bit Server VM GraalVM EE 22.3.0 (build 17.0.5+9-LTS-jvmci-22.3-b07, mixed mode, sharing)
复制代码


如果是在 mac 环境下,还需要执行下面的命令来解除对 graalvm 的隔离限制:


 sudo xattr -r -d com.apple.quarantine /path/to/graalvm
复制代码


否则在使用中就会遇到下面的问题:


添加 Native Image 支持

我们安装 GraalVM 的目的就是使用它的 native Image 特性。native image 是一个单独的 jar 包,我们可以执行下面的命令来进行安装:


gu install native-image
复制代码


其中 gu 就是/Library/Java/JavaVirtualMachines/graalvm-ee-java17-22.3.0/Contents/Home/bin 中的命令。


下载的过程中还需要输入一个有效的邮件,并进行邮箱校验。然后一路 ENTER 就可以了。


当然,你还可以把 Oracle GraalVM Enterprise Edition Native Image 下载到本地,然后使用 gu install -L 来进行本地安装。


好了,到目前为止,一切准备妥当,我们接下来看看如何把 spring boot3 的应用打包成为 native image 吧。

构建 spring boot3 应用

这里我们使用的是 maven,所以需要添加下面的 spring boot3 的依赖:


    <parent>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-parent</artifactId>        <version>3.0.1</version>        <relativePath/>     </parent>
复制代码


因为要构建 native image,所以我们还需要用到下面的一个 native-maven-plugin 插件:


            <plugin>                <groupId>org.graalvm.buildtools</groupId>                <artifactId>native-maven-plugin</artifactId>            </plugin>
复制代码


这里我们只创建了一个非常简单的 main 方法:


@SpringBootApplicationpublic class NativeImageApplication {
public static void main(String[] args) { SpringApplication.run(NativeImageApplication.class, args); }
}
复制代码


然后,我们尝试运行 mvn native:build 来构建 spring boot3 应用程序。


记得在 build 之前一定先要编译好项目。


很可惜,你会发现下面的异常:


[INFO] --- native-maven-plugin:0.9.19:build (default-cli) @ native-image ---[WARNING] 'native:build' goal is deprecated. Use 'native:compile-no-fork' instead.[INFO] Found GraalVM installation from JAVA_HOME variable....Error: Please specify class (or <module>/<mainclass>) containing the main entry point method. (see --help)
复制代码


从上面的异常我们发现了两个问题,第一个问题是一个警告,它推荐我们使用 native:compile-no-fork。


第二个问题是说找不到 mainclass,根据异常信息,我们在 pom 的 plugin 中添加下面的配置信息,如下所示:


<plugin>                <groupId>org.graalvm.buildtools</groupId>                <artifactId>native-maven-plugin</artifactId>                <configuration>                    <!-- imageName用于设置生成的二进制文件名称 -->                    <imageName>${project.artifactId}</imageName>                    <!-- mainClass用于指定main方法类路径 -->                    <mainClass>com.flydean.nativeimage.NativeImageApplication</mainClass>                    <buildArgs>                        --no-fallback                    </buildArgs>                </configuration>                <executions>                    <execution>                        <id>build-native</id>                        <goals>                            <goal>compile-no-fork</goal>                        </goals>                        <phase>package</phase>                    </execution>                </executions>            </plugin>
复制代码


然后重新运行 mvn native:compile-no-fork:


GraalVM Native Image: Generating 'native-image' (executable)...========================================================================================================================[1/7] Initializing...                                                                                    (4.3s @ 0.25GB) Version info: 'GraalVM 22.3.0 Java 17 EE' Java version info: '17.0.5+9-LTS-jvmci-22.3-b07' C compiler: cc (apple, arm64, 14.0.0) Garbage collector: Serial GC 1 user-specific feature(s) - org.springframework.aot.nativex.feature.PreComputeFieldFeatureField org.apache.commons.logging.LogAdapter#log4jSpiPresent set to true at build timeField org.apache.commons.logging.LogAdapter#log4jSlf4jProviderPresent set to true at build timeField org.apache.commons.logging.LogAdapter#slf4jSpiPresent set to true at build timeField org.apache.commons.logging.LogAdapter#slf4jApiPresent set to true at build timeField org.springframework.core.NativeDetector#imageCode set to true at build timeField org.springframework.core.KotlinDetector#kotlinPresent set to false at build timeField org.springframework.core.KotlinDetector#kotlinReflectPresent set to false at build timeField org.springframework.format.support.DefaultFormattingConversionService#jsr354Present set to false at build timeField org.springframework.cglib.core.AbstractClassGenerator#imageCode set to true at build time[2/7] Performing analysis...  [**********]                                                              (24.8s @ 4.57GB)  10,266 (89.50%) of 11,470 classes reachable  16,675 (63.53%) of 26,248 fields reachable  53,776 (60.71%) of 88,575 methods reachable     469 classes,   140 fields, and 2,281 methods registered for reflection      63 classes,    69 fields, and    55 methods registered for JNI access       5 native libraries: -framework CoreServices, -framework Foundation, dl, pthread, z[3/7] Building universe...                                                                               (5.0s @ 2.72GB)[4/7] Parsing methods...      [**]                                                                       (4.4s @ 2.42GB)[5/7] Inlining methods...     [***]                                                                      (1.3s @ 3.87GB)[6/7] Compiling methods...    [********]                                                                (70.0s @ 1.04GB)[7/7] Creating image...                                                                                  (4.7s @ 3.35GB)  30.27MB (58.75%) for code area:    30,771 compilation units  20.50MB (39.79%) for image heap:  305,579 objects and 93 resources 769.52KB ( 1.46%) for other data  51.52MB in total------------------------------------------------------------------------------------------------------------------------Top 10 packages in code area:                               Top 10 object types in image heap:   2.02MB com.oracle.svm.core.code                             5.79MB byte[] for code metadata   1.77MB sun.security.ssl                                     2.31MB byte[] for java.lang.String   1.29MB java.util                                            2.09MB byte[] for general heap data 929.52KB java.lang.invoke                                     2.07MB java.lang.String 925.96KB com.sun.crypto.provider                              1.76MB java.lang.Class 802.99KB java.lang                                          671.09KB byte[] for embedded resources 633.35KB sun.nio.ch                                         567.26KB byte[] for reflection metadata 625.89KB java.util.concurrent                               481.22KB com.oracle.svm.core.hub.DynamicHubCompanion 601.86KB org.apache.tomcat.util.net                         450.06KB java.util.HashMap$Node 594.48KB sun.security.x509                                  401.78KB java.util.concurrent.ConcurrentHashMap$Node  20.02MB for 397 more packages                                3.40MB for 2297 more object types------------------------------------------------------------------------------------------------------------------------                        9.5s (7.9% of total time) in 50 GCs | Peak RSS: 3.75GB | CPU load: 4.39------------------------------------------------------------------------------------------------------------------------Produced artifacts: /Users/learn-springboot3/learn-springboot3/native-image/target/native-image (executable) /Users/learn-springboot3/learn-springboot3/native-image/target/native-image.build_artifacts.txt (txt)========================================================================================================================Finished generating 'native-image' in 2m 0s.[INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ------------------------------------------------------------------------[INFO] Total time:  02:01 min[INFO] Finished at: 2023-01-05T20:43:39+08:00[INFO] ------------------------------------------------------------------------
复制代码


经过漫长的等待,我们终于 build 完成了。


因为我们的 artifactId 叫做 native-image,所以最终在 target 目录下面生成了一个叫做 native-image 的可执行文件:


.├── classes│   ├── application.properties│   └── com│       └── flydean│           └── nativeimage│               └── NativeImageApplication.class├── generated-sources│   └── annotations├── generated-test-sources│   └── test-annotations├── maven-archiver│   └── pom.properties├── maven-status│   └── maven-compiler-plugin│       ├── compile│       │   └── default-compile│       │       ├── createdFiles.lst│       │       └── inputFiles.lst│       └── testCompile│           └── default-testCompile│               ├── createdFiles.lst│               └── inputFiles.lst├── native-image├── native-image-0.0.1-SNAPSHOT.jar├── native-image-0.0.1-SNAPSHOT.jar.original├── native-image.build_artifacts.txt├── surefire-reports│   ├── TEST-com.flydean.nativeimage.NativeImageApplicationTests.xml│   └── com.flydean.nativeimage.NativeImageApplicationTests.txt└── test-classes    └── com        └── flydean            └── nativeimage                └── NativeImageApplicationTests.class
20 directories, 14 files
复制代码


如果你这时候运行 target/native-image,那么很可能得到下面的异常:


[main] DEBUG org.springframework.context.aot.AotApplicationContextInitializer - Initializing ApplicationContext with AOT[main] ERROR org.springframework.boot.SpringApplication - Application run failedjava.lang.IllegalArgumentException: Could not find class [com.flydean.nativeimage.NativeImageApplication__ApplicationContextInitializer]        at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:333)
复制代码


这是因为我们缺少一些 spring boot 的 AOT 元文件信息,正确的做法是使用下面的命令:


mvn clean package -Pnative
复制代码


它实际上执行的是下面的几个命令:


mvn spring-boot:process-aotmvn spring-boot:process-test-aotmvn spring-boot:build-image
复制代码


最终我们得到编译好的 native-image 信息,运行得到下面的结果:


2023-01-05T17:07:11.692+08:00  INFO 69299 --- [           main] c.f.nativeimage.NativeImageApplication   : Starting AOT-processed NativeImageApplication using Java 17.0.5 with PID 69299 (/Users/wayne/data/git/ddean2009/learn-springboot3/learn-springboot3/native-image/target/native-image started by wayne in /Users/wayne/data/git/ddean2009/learn-springboot3/learn-springboot3/native-image)2023-01-05T17:07:11.693+08:00  INFO 69299 --- [           main] c.f.nativeimage.NativeImageApplication   : No active profile set, falling back to 1 default profile: "default"2023-01-05T17:07:11.709+08:00  INFO 69299 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)2023-01-05T17:07:11.710+08:00  INFO 69299 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]2023-01-05T17:07:11.710+08:00  INFO 69299 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.4]2023-01-05T17:07:11.717+08:00  INFO 69299 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext2023-01-05T17:07:11.717+08:00  INFO 69299 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 24 ms2023-01-05T17:07:11.729+08:00  INFO 69299 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''2023-01-05T17:07:11.729+08:00  INFO 69299 --- [           main] c.f.nativeimage.NativeImageApplication   : Started NativeImageApplication in 0.053 seconds (process running for 0.072)
复制代码

总结

从运行情况来看,native-image 的启动速度非常快,应该可以提升不少的性能。


感兴趣的小伙伴赶紧用起来吧。


本文的例子https://github.com/ddean2009/learn-springboot3

发布于: 刚刚阅读数: 3
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020-06-07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
在spring boot3中使用native image_spring_程序那些事_InfoQ写作社区