Building Native Image With Quarkus and GraalVM
In this blog post I will explain how to build native image with Quarkus framework on Windows and Linux.
One of the main features of Quarkus framework is support for the native image building. With Native Image feature, we can build self executable Java exe which does not require the JDK to run the program.
Quarkus provides support for compiling applications to native executables using the GraalVM native-image compiler.
What is Native Image
Native Image is a technology to ahead-of-time compile Java code to a standalone executable, called a native image. This executable includes the application classes, classes from its dependencies, runtime library classes, and statically linked native code from JDK. It does not run on the Java VM, but includes necessary components like memory management, thread scheduling, and so on from a different runtime system, called “Substrate VM”. Substrate VM is the name for the runtime components (like the deoptimizer, garbage collector, thread scheduling etc.). The resulting program has faster startup time and lower runtime memory overhead compared to a JVM.
Note
For building Native image in quarkus , you need computer/laptop with more than 8GB RAM. If you have less RAM , you might get OutOfMemoryError
Fatal error:org.graalvm.compiler.debug.GraalError: java.lang.OutOfMemoryError: GC overhead limit exceeded
In one of my previous blog post, I explained how to build Rest API using Quarkus framework. I am going to use the same project for building the native image.
First we need to install additional softwares for building the Native Images
i) Download and Install GraalVM CE 22.2.0 version ( Java 11/17 based) from here
Next we need to set up the environment variables
Set GRAALVM_HOME
environment variable to the GraalVM installation directory
Linux
export GRAALVM_HOME=<GraalVM instllation directory>
Code language: Java (java)
Windows
Set environment variables from control panel
ii) Install Native Image Tools
Install the native-image
tool using gu install
Linux
${GRAALVM_HOME}/bin/gu install native-image
Code language: Shell Session (shell)
Windows
%GRAALVM_HOME%\bin\gu install native-image
Code language: Shell Session (shell)
Linux
On Linux we need to install following libraries
# dnf (rpm-based)
sudo dnf install gcc glibc-devel zlib-devel libstdc++-static
# Debian-based distributions:
sudo apt-get install build-essential libz-dev zlib1g-dev
Code language: Shell Session (shell)
Run the below command from project folder to create native image
mvnw package -Pnative
Code language: Shell Session (shell)
On Linux, native image can be created without the GraalVM by running the below command. Below command create native image in target
folder.
./mvnw package -Pnative -Dquarkus.native.container-build=true
Code language: Shell Session (shell)
Let’s start the application by running the native image
$ ./target/quarkus-hello-world-1.0.0-SNAPSHOT-runner
Code language: Java (java)
Windows
Download and Install Visual Studio 2019 Visual C++ Build Tools and Visual Studio 2019 community edition
Note : According to Quarkus documentation to build native image we need to install only Visual Studio 2017 Visual C++ Build Tools
but on my Windows 10 system, I could build native image only after installing 2019 Visual C++ Build Tools
and Visual Studio Community Edition
Now search for x86_x64 cross tools command prompt for VS 2019
and open the command prompt by clicking on it.
Now navigate to project folder and run following command
mvnw package -Pnative
Code language: Shell Session (shell)
Note
As per Quarkus documentation, we can build the native image with the following command. without opening VS command prompt. But I it did not work for me.
cmd /c call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat" && mvn package -Pnative
The above command will create exe file in the target folder
The native executable will contain the application code, required libraries, Java APIs, and a reduced version of a VM. The smaller VM base improves the start up time of the application and produces a minimal disk footprint.
If we run the exe file, it will start the application at port 8080
Let’s test the REST API again
Building Docker image
We can build the Docker image of native image built in above step.
We can utilize the Docker file (Dockerfile.native) provided by starter project
docker build -f src/main/docker/Dockerfile.native -t quarkus/quarkus-hello-world .
Code language: Shell Session (shell)
Then run the container using:
docker run -i --rm -p 8080:8080 quarkus/quarkus-hello-world
Code language: Shell Session (shell)
Troubleshooting
Generating native image with GraalVm is time and memory consuming operation.
If you have less then 8GB ram , you might get following errors in your local system or CI/CD while building native image
Fatal error:org.graalvm.compiler.debug.GraalError: java.lang.OutOfMemoryError: GC overhead limit exceeded
Error: Image build request failed with exit status 137
To over come the problems , you can use following options. Remember that if you limit the memory consumption build time will increase.
- Limit the memory consumption by providing additional params to quarkus-maven-plugin.
<quarkus.native.additiona-build-args>-J-Xmx4G</quarkus.native.additiona-build-args>
<profile> <id>native</id> <activation> <property> <name>native</name> </property> </activation> <build> <plugins> <plugin> <artifactId>maven-failsafe-plugin</artifactId> <version>${surefire-plugin.version}</version> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> <configuration> <systemPropertyVariables> <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path> <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager> <maven.home>${maven.home}</maven.home> </systemPropertyVariables> </configuration> </execution> </executions> </plugin> </plugins> </build> <properties> <quarkus.package.type>native</quarkus.package.type> <quarkus.native.additiona-build-args>-J-Xmx4G</quarkus.native.additiona-build-args> </properties> </profile>
2. you can limit memory usage from Quarkus.
In your src/main/resources/application.properties file, just set:
quarkus.native.native-image-xmx=4G
3. Pass the parameter during build process
mvn package -Dnative -Dquarkus.native.native-image-xmx=4G
References
- https://www.redhat.com/en/topics/cloud-native-apps/what-is-quarkus
- https://quarkus.io/get-started/
- https://www.graalvm.org/reference-manual/native-image/
- https://stackoverflow.com/questions/57166853/quarkus-native-executable-build-high-memory-consumption