在spring boot3中使用native image
簡介
在之前spring boot3文章中我們介紹了,spring boot3的一個(gè)重要特性就是支持把spring boot3的應(yīng)用編譯成為GraalVM的Native Image。
今天我們用具體的例子來給大家演示一下如何正確的將spring boot3的應(yīng)用編譯成為native image。
安裝GraalVM
如果要把spring boot3的app編譯成為native應(yīng)用,需要GraalVM的支持。
什么是GraalVM呢?
從名字就可以看出來GraalVM是一個(gè)虛擬機(jī),它的主要目標(biāo)就是提升java應(yīng)用程序的性能,并且消耗更少的資源。
它在java HotSpot JVM的基礎(chǔ)上添加了JIT編譯器和AOT來實(shí)現(xiàn)將應(yīng)用編譯成為本地可執(zhí)行文件。除了java之外,GraalVM還支持JavaScript、Ruby、Python等多種編程語言。
所以,為什么要用GraalVM呢?一個(gè)字:快。
安裝GraalVM也比較簡單,我們進(jìn)入它的官方下載頁面下載對應(yīng)的版本即可:https://www.oracle.com/downloads/graalvm-downloads.html。
GraalVM跟JDK一樣也有兩個(gè)版本,社區(qū)版和企業(yè)版本,大家可以根據(jù)需要自行選擇。
要注意的是spring boot3需要GraalVM 22.3以上的版本支持,大家可不要下載錯(cuò)了。
下載完成之后,我們可以像正常安裝JDK一樣來安裝GraalVM,這里以mac為例,假如我們安裝的目錄是/Library/Java/JavaVirtualMachines/graalvm-ee-java17-22.3.0,那么我們需要配置對應(yīng)的JAVA_HOME和PATH環(huán)境變量如下:
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中有一個(gè)非常重要的命令叫做gu,如果不添加PATH,那么在使用中就可能遇到下面的異常:
'gu' tool wasn't found. This probably means that JDK at isn't a GraalVM distribution.
安裝完畢之后可以通過下面的命令來進(jìn)行驗(yàn)證:
java -version
java version "17.0.5" 2022-10-18 LTS
Java(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環(huán)境下,還需要執(zhí)行下面的命令來解除對graalvm的隔離限制:
sudo xattr -r -d com.apple.quarantine /path/to/graalvm
否則在使用中就會遇到下面的問題:
添加Native Image支持
我們安裝GraalVM的目的就是使用它的native Image特性。native image是一個(gè)單獨(dú)的jar包,我們可以執(zhí)行下面的命令來進(jìn)行安裝:
gu install native-image
其中g(shù)u就是/Library/Java/JavaVirtualMachines/graalvm-ee-java17-22.3.0/Contents/Home/bin中的命令。
下載的過程中還需要輸入一個(gè)有效的郵件,并進(jìn)行郵箱校驗(yàn)。然后一路ENTER就可以了。
當(dāng)然,你還可以把Oracle GraalVM Enterprise Edition Native Image下載到本地,然后使用gu install -L來進(jìn)行本地安裝。
好了,到目前為止,一切準(zhǔn)備妥當(dāng),我們接下來看看如何把spring boot3的應(yīng)用打包成為native image吧。
構(gòu)建spring boot3應(yīng)用
這里我們使用的是maven,所以需要添加下面的spring boot3的依賴:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.1</version>
<relativePath/>
</parent>
因?yàn)橐獦?gòu)建native image,所以我們還需要用到下面的一個(gè)native-maven-plugin插件:
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
</plugin>
這里我們只創(chuàng)建了一個(gè)非常簡單的main方法:
@SpringBootApplication
public class NativeImageApplication {
public static void main(String[] args) {
SpringApplication.run(NativeImageApplication.class, args);
}
}
然后,我們嘗試運(yùn)行 mvn native:build來構(gòu)建spring boot3應(yīng)用程序。
記得在build之前一定先要編譯好項(xiàng)目。
很可惜,你會發(fā)現(xiàn)下面的異常:
[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)
從上面的異常我們發(fā)現(xiàn)了兩個(gè)問題,第一個(gè)問題是一個(gè)警告,它推薦我們使用native:compile-no-fork。
第二個(gè)問題是說找不到mainclass,根據(jù)異常信息,我們在pom的plugin中添加下面的配置信息,如下所示:
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<configuration>
<!-- imageName用于設(shè)置生成的二進(jìn)制文件名稱 -->
<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>
然后重新運(yùn)行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.PreComputeFieldFeature
Field org.apache.commons.logging.LogAdapter#log4jSpiPresent set to true at build time
Field org.apache.commons.logging.LogAdapter#log4jSlf4jProviderPresent set to true at build time
Field org.apache.commons.logging.LogAdapter#slf4jSpiPresent set to true at build time
Field org.apache.commons.logging.LogAdapter#slf4jApiPresent set to true at build time
Field org.springframework.core.NativeDetector#imageCode set to true at build time
Field org.springframework.core.KotlinDetector#kotlinPresent set to false at build time
Field org.springframework.core.KotlinDetector#kotlinReflectPresent set to false at build time
Field org.springframework.format.support.DefaultFormattingConversionService#jsr354Present set to false at build time
Field 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.HashMapNode
594.48KB sun.security.x509 401.78KB java.util.concurrent.ConcurrentHashMapNode
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] ------------------------------------------------------------------------
經(jīng)過漫長的等待,我們終于build完成了。
因?yàn)槲覀兊腶rtifactId叫做native-image,所以最終在target目錄下面生成了一個(gè)叫做native-image的可執(zhí)行文件:
.
├── 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
如果你這時(shí)候運(yùn)行target/native-image,那么很可能得到下面的異常:
[main] DEBUG org.springframework.context.aot.AotApplicationContextInitializer - Initializing ApplicationContext with AOT
[main] ERROR org.springframework.boot.SpringApplication - Application run failed
java.lang.IllegalArgumentException: Could not find class [com.flydean.nativeimage.NativeImageApplication__ApplicationContextInitializer]
at org.springframework.util.ClassUtils.resolveClassName(ClassUtils.java:333)
這是因?yàn)槲覀內(nèi)鄙僖恍﹕pring boot的AOT元文件信息,正確的做法是使用下面的命令:
mvn clean package -Pnative
它實(shí)際上執(zhí)行的是下面的幾個(gè)命令:
mvn spring-boot:process-aot
mvn spring-boot:process-test-aot
mvn spring-boot:build-image
最終我們得到編譯好的native-image信息,運(yùn)行得到下面的結(jié)果:
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 WebApplicationContext
2023-01-05T17:07:11.717+08:00 INFO 69299 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 24 ms
2023-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)
總結(jié)
從運(yùn)行情況來看,native-image的啟動(dòng)速度非???,應(yīng)該可以提升不少的性能。
感興趣的小伙伴趕緊用起來吧。
本文的例子https://github.com/ddean2009/learn-springboot3
作者:程序那些事
歡迎關(guān)注微信公眾號 :程序那些事