Introduction to GraalVM Native Image
Abstract
GraalVM is a high-performance, universal polyglot VM that can run applications written in various programming languages. It can generate native images using ahead-of-time (AOT) compilation. Native images can improve startup time and reduce memory footprint of applications. This article shows how to use GraalVM to build a native image of a simple program written in Java and then compares file space usage, startup time and memory usage of the native application versus its the managed version.
1. What is GraalVM?
GraalVM is a high-performance, universal polyglot VM that can run applications written in dynamic languages (e.g. Python, JavaScript, R, Ruby), LLVM-based languages (e.g. C, C++) or JVM-based languages (e.g. Scala, Kotlin, Clojure, Java).
GraalVM can be embedded in both managed and native applications. It can run either standalone or in the context of OpenJDK, Node.js, Oracle Database, or MySQL.
GraalVM can generate native images using ahead-of-time (AOT) compilation. Native images can improve startup time and reduce memory footprint of applications.
GraalVM is available in two editions - Community Edition (CE) and Enterprise Edition (EE). The Community Edition (CE) 19.0.0 has open-source license and is free for production use.
GraalVM 19.0.0 is based on JDK version 8u212. Work is in progress [11] to add support for Java 11+.
2. What is GraalVM Native Image?
GraalVM Native Image allows you to ahead-of-time compile Java code to a standalone executable, called a native image. This executable includes the application, the libraries, the JDK and does not run on the Java VM, but includes necessary components like memory management and thread scheduling from a different virtual machine, called “Substrate VM”. … The resulting program has faster startup time and lower runtime memory overhead compared to a Java VM.
A native image is preferred over the JVM when,
-
startup time and memory footprint is important.
-
Java code needs to be embedded with other native applications.
Due to fast startup time and low memory footprint, native images are well-suited for CLI and serverless applications.
Frameworks such as Fn Project, Micronaut and Helidon [4] already support GraalVM native images.
There is work in progress to add support for GraalVM in Netty [5],[9], Spring Framework [6], Spring Boot [10], Vert.x [7],[8] and more.
GraalVM Native Image for Java has limitations. GraalVM Native Image is available as an early adopter plugin. This means its functionality is subject to change while its specification is being developed by the Java community. |
3. Preparations
3.1. Platform and tools used in this experiment
-
GraalVM Community Edition 19.0.0
-
GraalVM
native-image
plugin -
Maven plugin
com.oracle.substratevm:native-image-maven-plugin
-
macOS Mojave
-
Xcode command line tools
3.2. Install GraalVM
-
Download GraalVM Community Edition 19.0.0.
-
Unpack the archive
$ tar xf graalvm-ce-darwin-amd64-19.0.0.tar.gz -C <GRAALVM_INSTALL_DIR>
-
Set
GRAALVM_HOME
environment variable.The exact path of $GRAALVM_HOME
will depend on the operating system.On macOS$ export GRAALVM_HOME="<GRAALVM_INSTALL_DIR>/Contents/Home"
3.3. Install Xcode command line tools
This step is required only on macOS. In the absence of Xcode command line tools, native image creation may fail with error
|
If Xcode command line tools are not install on your macOS machine, install them.
$ xcode-select --install
4. Native image
4.1. Create a simple program
-
Create a Maven-based
hello-world
module. -
Configure this module to use Java 8
pom.xml<properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties>
-
Create a Hello World class
package rahulb.experiments.graalvm.helloworld; public class Main { public static void main(String[] args) { System.out.println("Hello World!"); } }
4.2. Build a native image
-
Add
native-image
plugin to GraalVM using GraalVM Updater tool.$ ${GRAALVM_HOME}/bin/gu install native-image
-
Add
com.oracle.substratevm:native-image-maven-plugin
to the buildpom.xml<properties> <graalvm.version>19.0.0</graalvm.version> ... </properties> <build> <plugins> <plugin> <groupId>com.oracle.substratevm</groupId> <artifactId>native-image-maven-plugin</artifactId> <version>${graalvm.version}</version> <executions> <execution> <id>build-native-image</id> <phase>package</phase> <goals> <goal>native-image</goal> </goals> <configuration> <mainClass>rahulb.experiments.graalvm.helloworld.Main</mainClass> <imageName>hello-world</imageName> <buildArgs>--no-server --no-fallback</buildArgs> (1) </configuration> </execution> </executions> </plugin> </plugins> </build>
1 --no-server
means 'do not use image-build server'.--no-fallback
means 'build standalone image or report failure' -
Set
JAVA_HOME
environment variable equal to$GRAALVM_HOME
$ export JAVA_HOME="${GRAALVM_HOME}"
-
Build the package
$ mvn clean package
This will generate a native image named
hello-world
intarget
directory of the Maven project.On my machine, it took 87 seconds to build the native image.
The GraalVM version used to build a native image can be determined from the native image file.
|
4.3. Comparisons - native application vs. managed application
4.3.1. File space usage
To determine the file space usage of the managed program, package the program and
the Java Runtime Environment (JRE) using jpackage
tool.
See Using jpackage tool for instructions to generate the package.
$ du --summarize --dereference --human-readable ./hello-world.app/
123M ./hello-world.app/ (1)
1 | The application and the JRE together occupy 123 MB of file space. |
Now, let us determine the size of the native image.
$ du --dereference --human-readable <PROJECT_HOME>/target/hello-world
2.4M ./target/hello-world (1)
1 | The native image occupies 2.4 MB of file space. |
4.3.2. Startup time
$ time ./target/hello-world
Hello World!
real 0m0.005s (1)
user 0m0.002s
sys 0m0.002s
1 | The native application finished in 5 milliseconds |
$ time ${JDK11_HOME}/bin/java -cp ./target/hello-world-1.0.0.jar rahulb.experiments.graalvm.helloworld.Main
Hello World!
real 0m0.103s (1)
user 0m0.103s
sys 0m0.024s
1 | The managed application finished in 103 milliseconds |
4.3.3. Memory usage
$ gtime -f "\nMax resident set size: %M KB" target/hello-world
Hello World!
Max resident set size: 1704 KB (1)
1 | Maximum RSS was about 1.7 MB |
$ gtime -f "\nMax resident set size: %M KB" java -cp target/hello-world-1.0.0.jar rahulb.experiments.graalvm.helloworld.Main
Hello World!
Max resident set size: 32840 KB (1)
1 | Maximum RSS was about 32 MB |
5. Summary
This article showed how to use GraalVM to build a native image of a simple program written in Java. It also showed how a native application occupies lesser file space, starts up faster and has lower memory footprint than its managed version.
Appendix A: Using jpackage tool
A.1. Install jpackage tool
-
Download
jpackage
tool -
Unpack the archive
$ tar xzf openjdk-13-jpackage+49_osx-x64_bin.tar.gz
A.2. Create a package using jpackage tool
-
Prepare for packaging
Example$ mkdir -p /tmp/packaging-workspace/input $ mkdir -p /tmp/packaging-workspace/output $ cd /tmp/packaging-workspace $ cp <PROJECT_HOME>/target/hello-world-1.0.0.jar ./input
-
Generate a package
Example$ cd /tmp/packaging-workspace $ jpackage create-app-image \ --output ./output \ --input ./input \ --name hello-world \ --main-class rahulb.experiments.graalvm.helloworld.Main \ --main-jar hello-world-1.0.0.jar
This generates a directory named
hello-world.app
in the output directory.Package contents$ cd /tmp/packaging-workspace/output $ find . -maxdepth 4 -mindepth 3 -type d ./hello-world.app/Contents/MacOS (1) ./hello-world.app/Contents/Resources ./hello-world.app/Contents/runtime ./hello-world.app/Contents/runtime/Contents (2) ./hello-world.app/Contents/Java (3)
1 Contains the application launcher 2 Contains Java Runtime Environment (JRE) 3 Contains application JARs -
Test the package by launching the application
Example$ cd /tmp/packaging-workspace/output $ ./hello-world.app/Contents/MacOS/hello-world (1) Hello World! (2)
1 Application launcher 2 Application output