Native Image Quick Reference v2

Olga Gupalo
graalvm
Published in
7 min readOct 31, 2022

--

Native Image is one of the most popular features of GraalVM, and it has been quite some time since we published the first Native Image Quick Reference. Since then, many feature requests have been implemented, new command-line options introduced or deprecated, and tooling support has been expanded. We have assembled for you the most valuable options, commands, and links in this one-page summary:

This quick reference fits on a sheet of A4 paper, so you can print the PDF version and use it as a handy reminder. If your printer uses US Letter paper formats, download this version.

Before you can start using Native Image, download the GraalVM JDK. The easiest way to do so is to run the GraalVM JDK Downloader:

$ bash <(curl -sL https://get.graalvm.org/jdk)

Build Native Executables and Shared Libraries

Native Image can turn Java applications into native executables and shared libraries. These binary files contain user code, dependencies, and necessary parts of the Java runtime (for example, the garbage collector). An input source can be either a JAR file with all your application’s dependencies, a module, or the application’s main class. Yes, GraalVM Native Image now supports the Java Platform Module System!

$ native-image --jar Foo.jar                # for a JAR
$ native-image -p Foo.jar -m org.foo.Main # for a module
$ native-image -cp Foo.jar org.foo.Main # for a class

Here we would like to emphasize that you can use $JAVA_HOME/bin/native-image the same way you would use $JAVA_HOME/bin/java. The default behavior is aligned with the java command. See how you can build an executable from two Java modules: base-module.jar and main-module.jar, where the module demo.app contains the main class:

$ native-image -p base-module.jar:main-module.jar -m demo.app

Building native executables can take some time as Native Image needs to statically analyze your entire application and its dependencies, optimize, and then compile all its reachable code. To speed up this process, you can enable the quick build mode, -Ob (capital “O”, lower case “b”), for development purposes: the compiler will work in economy mode with fewer optimizations, resulting in much faster compilation times. So it optimizes build time at the cost of run time performance and memory usage and, thus, is not recommended for production.

$ native-image -Ob MyApp

You can also set a name for the resulting binary with the new option -o. For example:

$ native-image -jar Foo.jar -o myapp

Note that builds no longer fail if a class cannot be found on the classpath or module path. If such a class, however, is explicitly required at run time, the native executable will fail (for example, with a java.lang.NotClassDefFoundError). To prevent such scenarios, Native Image provides a new --link-at-build-time option to ensure a specific list of packages and classes are available and “linked” at build time.

To take full advantage of the AOT approach, Native Image allows classes to be initialized at build time, which reduces necessary work at run time and improves performance. Class initialization can be manually configured, using the --initialize-at-run-time=my.package,some.class and --initialize-at-build-time=my.package, some.class options. The static fields from initialized classes are stored in the produced executable. Read more in the documentation.

As already mentioned, you can build native executables and shared libraries. To build the latter, pass the argument --shared. Then you can call/load this library from, for example, a C/C++ application, using the Native Image C API. Besides these two binary types, you can also statically linking against a libc implementation. Depending on the deployment environment, you can link against glib, musl, or bionic: --static --libc=<glib | musl | bionic>. The linked binary is easy to distribute and deploy on a slim or distroless container (a scratch container). Find an example here.

Another action that can help optimize the performance is switching to another garbage collection implementation. Native Image now has a new policy for Serial GC, reducing the memory footprint of applications at run time, and also a new Epsilon GC. Serial GC is the default GC. The Epsilon GC is a no-op garbage collector for short-running applications. To enable the Epsilon GC, specify the option --gc=epsilon. GraalVM Enterprise Native Image also supports the G1 GC, which can be enabled with the --gc=G1 option.

Moreover, GraalVM Enterprise Native Image enables you to embed a Software Bill of Materials (SBOM) into a native executable to aid vulnerability scanners. For that, use the --enable-sbom build-time option (currently, only the CycloneDX format is supported). When this option was enabled, you can use the Inspection tool to extract the compressed SBOM with this command:

$ native-image-inspect --sbom <path_to_binary>

Another essential option is --enable-url-protocols which accepts a comma-separated list of URL protocols. The file and resource protocols are enabled by default; http and https can be added on demand. This way your binary will only include the features it needs, which helps keep the overall size small. The option --install-exit-handlers is to include signal handlers, which is preferable for building native executables in Docker containers. The last thing we’d like to mention is that you can build polyglot native executables by enabling specific GraalVM language runtimes: --language:<java | js | python | ruby | wasm>.

Besides optimizing performance by tuning the garbage collection policy, another method is to enable Profile-Guided Optimizations (available with GraalVM Enterprise) to improve peak performance and throughput. First, you build an instrumented native executable: native-image --pgo-instrument MyApp. Then you run it to record profiles: ./myapp. Finally, you build an optimized native executable based on profiling data: native-image --pgo=default.iprof MyApp .

A full list of Native Image options can be obtained with the --help and --help-extra options.

Native Image Integrations

In the last year, we built many new integrations for Native Image. In collaboration with the JUnit, Micronaut, and Spring teams, we developed the Native Build Tools that provide Gradle and Maven plugins for Native Image with out-of-the-box support for native JUnit 5 testing. These plugins make building, testing, and running Java applications as native executables a straightforward experience. The workflow is familiar to any Java developer: you register the plugin in pom.xml or build.gradle, enable the plugin’s repository, and voila: you can build a native executable directly with Maven, ./mvnw -Pnative package, or Gradle, ./gradlew nativeRun. Note that you can pass any Native Image option in the plugin's configuration, too! For example:

<buildArgs>
<arg>--enable-url-protocols=https</arg>
</buildArgs>

Check the exhaustive Maven and Gradle plugins’ documentation or find demos on the website.

Another new integration is the GitHub Action for GraalVM. It allows you to build native executables as part of your project’s automated deployment workflows running on GitHub Actions!

Enhanced Third-Party Library Support

When you build a native executable, it only includes elements discovered through static analysis. Your app may encounter a problem at run time if a required class, method, or field was not reachable at build time and as such not included in the binary. To help the static analysis discover all Java’s dynamic feature calls (reflection, proxies, etc.) you may need to provide additional configuration. There are three ways:

1. Use a framework with built-in Native Image support such as Micronaut, Helidon, Spring, and Quarkus.

2. Pull in the configuration automatically by using the Maven/Gradle plugin from the GraalVM Reachability Metadata Repository.

3. Provide configuration manually. The Tracing Agent can help in this process. Run a Java application with the agent to generate configuration and then build a native executable pulling in this configuration:

$ java -agentlib:native-image-agent=config-output-dir=/path/to/config-dir/ -jar MyApp.jar
$ native-image -jar MyApp.jar

Configuration files in META-INF/native-image on the class-path or module-path are included automatically.

Monitoring and Debugging

Monitoring applications at run time has been receiving a lot of attention from the team lately. We want to provide users with good tools that not only help them catch issues at build time but also help optimize performance at run time.

Native executables, built with the option --enable-monitoring, open many monitoring possibilities, allowing the VM to be inspected at run time. It accepts a comma-separated list of arguments including heapdump, jfr , jvmstat, or all (defaults to all). Then, for example, you can enable JFR and start recording JFR events at run time:

$ ./myapp -XX:+FlightRecorder \
-XX:StartFlightRecording=“filename=recording.jfr”

You can also dump the heap of a native executable at run time. There are different ways to dump the heap, like sending the SIGUSR1 signal, programmatically, or using a command-line option -XX:+DumpHeapAndExit. A heap dump will be created in the directory of your native executable and can be then analyzed with Java heap dump tools such as VisualVM. Learn more here.

Moreover, you can tune the garbage collector or gather GC logs:

$ ./myapp -Xmx<m> -Xmn<m>
$ ./myapp -XX:+PrintGC -XX:+VerboseGC

Last but not least, debugging support has also significantly improved. Build a native executable with debug information and with compiler optimizations disabled: -g -O0, and then debug it with GDB or any IDE that integrates GDB. IntelliJ IDEA, for example, has experimental support for Native Image debugging based on GDB. Feel free to give it a try and let us know what you think!

Conclusion

During the last several releases we focused our development efforts on reducing build times and memory footprint, and making the overall developer experience better. Building native executables of applications that depend on third-party libraries has become a lot easier thanks to the Native Image Build Tools and the GraalVM Reachability Metadata Repository. In this quick reference, we outlined the most frequently used options and new integrations for GraalVM Native Image over the last year.

We hope you found this helpful! If you have any feedback, get in touch on Slack, Twitter, or GitHub.


Co-authored by Fabio Niephaus

--

--