Use native image in spring boot3

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:

 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
copy

There is a very important command called gu in PATH. If you do not add PATH, you may encounter the following exceptions during use:

'gu' tool wasn't found. This probably means that JDK at isn't a GraalVM distribution.
copy

After the installation is complete, you can verify it with the following command:

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)
copy

If it is in a mac environment, you also need to execute the following command to remove the isolation restrictions on graalvm:

 sudo xattr -r -d com.apple.quarantine /path/to/graalvm
copy

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:

gu install native-image
copy

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:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.1</version>
        <relativePath/> 
    </parent>
copy

Because we want to build a native image, we also need to use the following native-maven-plugin plugin:

            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
            </plugin>
copy

Here we just create a very simple main method:

@SpringBootApplication
public class NativeImageApplication {

    public static void main(String[] args) {
        SpringApplication.run(NativeImageApplication.class, args);
    }

}
copy

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:

[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)
copy

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:

<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>
copy

Then re-run 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] ------------------------------------------------------------------------
copy

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:

.
├── 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
copy

If you run target/native-image at this time, you are likely to get the following exception:

[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)
copy

This is because we lack some AOT metafile information of spring boot. The correct way is to use the following command:

mvn clean package -Pnative
copy

It actually executes the following commands:

mvn spring-boot:process-aot
mvn spring-boot:process-test-aot
mvn spring-boot:build-image
copy

Finally, we get the compiled native-image information, and run it to get the following results:

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)
copy

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

Tags: Java Spring Maven Spring Boot

Posted by baselineace on Mon, 30 Jan 2023 21:11:36 +1030