Introduction
In the previous spring boot3 article, we introduced that an important feature of spring boot3 is to support the compilation of spring boot3 applications into GraalVM Native Image.
Today we use specific examples to show you how to correctly compile the spring boot3 application into a native image.
Install GraalVM
If you want to compile the spring boot3 app into a native application, you need the support of GraalVM.
What is GraalVM?
It can be seen from the name that GraalVM is a virtual machine, and its main goal is to improve the performance of java applications and consume less resources.
It adds a JIT compiler and AOT on the basis of the java HotSpot JVM to compile the application into a local executable file. In addition to java, GraalVM also supports JavaScript, Ruby, Python and other programming languages.
So, why use GraalVM? One word: fast.
Installing GraalVM is also relatively simple, we can go to its official download page to download the corresponding version: https://www.oracle.com/downloads/graalvm-downloads.html.
Like JDK, GraalVM also has two versions, community version and enterprise version, you can choose according to your needs.
It should be noted that spring boot3 requires the support of GraalVM version 22.3 and above, so don't download it wrong.
After the download is complete, we can install GraalVM like a normal JDK installation. Here we take mac as an example. If the directory we installed is /Library/Java/JavaVirtualMachines/graalvm-ee-java17-22.3.0, then we need to configure the corresponding The JAVA_HOME and PATH environment variables are as follows:
copyexport 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
There is a very important command called gu in PATH. If you do not add PATH, you may encounter the following exceptions during use:
copy'gu' tool wasn't found. This probably means that JDK at isn't a GraalVM distribution.
After the installation is complete, you can verify it with the following command:
copyjava -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)
If it is in a mac environment, you also need to execute the following command to remove the isolation restrictions on graalvm:
copysudo xattr -r -d com.apple.quarantine /path/to/graalvm
Otherwise, you will encounter the following problems in use:
Add Native Image support
The purpose of our installation of GraalVM is to use its native Image feature. The native image is a separate jar package, we can execute the following command to install it:
copygu install native-image
Among them, gu is the command in /Library/Java/JavaVirtualMachines/graalvm-ee-java17-22.3.0/Contents/Home/bin.
During the download process, you also need to enter a valid email and verify the email. Then just ENTER all the way.
Of course, you can also download the Oracle GraalVM Enterprise Edition Native Image locally, and then use gu install -L for local installation.
Well, so far, everything is ready, let's see how to package the spring boot3 application into a native image.
Build spring boot3 application
Here we are using maven, so we need to add the following spring boot3 dependencies:
copy<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.0.1</version> <relativePath/> </parent>
Because we want to build a native image, we also need to use the following native-maven-plugin plugin:
copy<plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> </plugin>
Here we just create a very simple main method:
copy@SpringBootApplication public class NativeImageApplication { public static void main(String[] args) { SpringApplication.run(NativeImageApplication.class, args); } }
Then we try to run mvn native:build to build the spring boot3 application.
Remember to compile the project before build ing.
Unfortunately, you will find the following exception:
copy[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)
From the above exception, we found two problems. The first problem is a warning, which recommends us to use native:compile-no-fork.
The second problem is that the mainclass cannot be found. According to the exception information, we add the following configuration information to the pom plugin, as follows:
copy<plugin> <groupId>org.graalvm.buildtools</groupId> <artifactId>native-maven-plugin</artifactId> <configuration> <!-- imageName Used to set the generated binary file name --> <imageName>${project.artifactId}</imageName> <!-- mainClass for specifying main method classpath --> <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>
Then re-run mvn native:compile-no-fork:
copyGraalVM 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] ------------------------------------------------------------------------
After a long wait, we finally finished the build.
Because our artifactId is called native-image, an executable file called native-image is finally generated under the target directory:
copy. ├── 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
If you run target/native-image at this time, you are likely to get the following exception:
copy[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)
This is because we lack some AOT metafile information of spring boot. The correct way is to use the following command:
copymvn clean package -Pnative
It actually executes the following commands:
copymvn spring-boot:process-aot mvn spring-boot:process-test-aot mvn spring-boot:build-image
Finally, we get the compiled native-image information, and run it to get the following results:
copy2023-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)
Summarize
Judging from the running situation, the startup speed of native-image is very fast, which should improve a lot of performance.
Interested friends hurry up and use it.
Example of this article https://github.com/ddean2009/learn-springboot3