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”. 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 Java VM.

Native Image Generator or native-image is a utility that processes all the classes of your application and their dependencies, including those from the JDK. It statically analyses these classes to determine which classes and methods are reachable and used during application execution. Then it passes all this reachable code as the input to the GraalVM compiler which ahead-of-time compiles it to the native binary.

GraalVM native-image supports JVM-based languages, e.g., Java, Scala, Clojure, Kotlin. The resulting native image can, optionally, execute dynamic languages like JavaScript, Ruby, R, or Python, but it does not pre-compile their code itself. Polyglot embeddings can also be compiled ahead-of-time. To inform native-image of guest languages used by an application, specify --language:<languageId> for each guest language used (e.g., --language:js).

Warning: GraalVM Native Image is available as an Early Adopter technology, meaning it can be used in production and will be supported by Oracle as part of the Oracle GraalVM Enterprise Edition subscription, but is not covered by Oracle’s standard warranty.

Install Native Image

Starting from GraalVM 19.0, Native Image was extracted from the base distribution. This functionality can be added to the core installation with GraalVM Updater utility in a similar way as other additional components.

For GraalVM Enterprise Edition users, download the Native Image component JAR file from Oracle Technology Network and install it by running gu -L install component.jar, where -L option, equivalent to --local-file, tells to install a component from a downloaded archive.

For GraalVM Community Edition users, install a component from the GitHub catalog by its name gu install native-image.

After this additional step, the native-image executable will become available in the bin directory, as for the previous releases.

Take a look at the native image generation or compiling a Java and Kotlin app ahead-of-time samples.

Prerequisites

For compilation native-image depends on the local toolchain, so please make sure: glibc-devel, zlib-devel (header files for the C library and zlib) and gcc are available on your system.

Another prerequisite to consider is the maximum heap size. Physical memory for running a JVM-based application may be insufficient to build a native image. For server-based image building we allow to use 80% of the reported physical RAM for all servers together, but never more than 14GB per server (for exact details please consult the native-image source code). If you run with --no-server option, you will get the whole 80% of what is reported as physical RAM as the baseline. This mode respects -Xmx arguments additionally.

Image Generation Options

The native-image command line needs to provide the class path for all classes using the familiar option from the java launcher: -cp is followed by a list of directories or .jar files, separated by :. The name of the class containing the main method is the last argument; or you can use -jar and provide a .jar file that specifies the main method in its manifest. The syntax of the native-image command is:

  • native-image [options] class to build an executable file for a class in the current working directory. Invoking it executes the native-compiled code of that class.

  • native-image [options] -jar jarfile to build an image for a jar file.

There is a command-line help available. Run native-image --help to get commands overview and native-image --help-extra to print help on non-standard options.

You may provide additional options to the native image building:

  • -cp and --class-path <class search path of directories and zip/jar files> help to search for class files through separated list of directories, JAR archives, and ZIP archives

  • -D<name>=<value> sets a system property for the JVM running the image generator

  • -J<flag> passes <flag> directly to the JVM running the image generator

  • -O<level> 0 – no optimizations, 1 – basic optimizations (default)

  • -ea and -da enables or disables assertions in the generated image

  • --allow-incomplete-classpath allows image building with an incomplete class path and reports type resolution errors at run time when they are accessed the first time, instead of during image building

  • --auto-fallback builds a stand-alone image if possible, which is a default setting, --no-fallback builds a stand-alone image or reports a failure, and --force-fallback forces building of a fallback image

  • --enable-all-security-services adds all security service classes to the generated image

  • --enable-http or --enable-https enables http or https support in the generated image

  • --enable-url-protocols provides a list of comma separated URL protocols to enable

  • --features=<fqcn> provides a comma-separated list of fully qualified feature implementation classes

  • --initialize-at-build-time=<comma separated list of class/package names> initializes classes and implicitly all of their superclasses during image building

  • --initialize-at-run-time=<comma separated list of class/package names> initializes classes and implicitly all of their superclasses at run time and not during image building

  • --report-unsupported-elements-at-runtime reports usage of unsupported methods and fields at run time when they are accessed the first time, instead of as an error during image building;

  • --shared builds a shared library

  • --static builds a statically linked executable. This option prevents linking against shared libraries. This requires the static libc library to be available on the host system. Warning: On systems other than Linux, this option has no effect

  • --verbose makes image building output more verbose

  • -g enables the debugging info generation. Warning: The option is available only with GraalVM Enterprise Edition

Macro Options

Macro-options are mainly helpful for polyglot capabilities of native images:

  • --language:nfi to make Truffle Native Function Interface language available

  • --language:regex to make Truffle Regular Expression engine available that exposes regular expression functionality in GraalVM supported languages

  • --language:js to make sure JavaScript is available as a language for the image

  • --language:llvm to make sure LLVM bitcode is available for the image

  • --language:python to make sure Python is available as a language for the image

  • --language:ruby to make sure Ruby is available as a language for the image

  • --language:R to make sure R is available as a language for the image

  • --tool:chromeinspector adds debugging support to a Truffle framework based language image

  • --tool:profiler adds profiling support to a Truffle framework based language image

Please note, the --language:python, --language:ruby and --language:R polyglot macro options become available once the corresponding languages engines are installed to the base GraalVM distribution.

Non-standard Options

Get acquainted with the non-standard native image building options, that are subject to change through a deprecation cycle:

  • --no-server tells to not use server-based image building

  • --server-list lists current image-build servers

  • --server-list-details lists current image-build servers with more details

  • --server-cleanup removes stale image-build servers entries

  • --server-shutdown shuts down image-build servers under current session ID

  • --server-shutdown-all shuts down all image-build servers

  • --server-session=<custom-session-name> uses custom session name instead of system provided session ID of the calling process

  • --verbose-server enables verbose output for image-build server handling

Server-side Options

  • --debug-attach[=<port>] attaches to debugger during image building (default port is 8000)

  • --dry-run outputs the command line that would be used for image building

  • --expert-options lists image build options for experts

  • --expert-options-all lists all image build options for experts (to be used at your own risk)

  • --configurations-path <search path of option-configuration directories> a separated list of directories to be treated as option-configuration directories

  • -V<key>=<value> provides values for placeholders in native-image.properties files

If the environment variable NATIVE_IMAGE_CONFIG_FILE is set to a Java properties file, a native image will use the defaults defined in there on each invocation.

Here is an example of configuration file (saved as ~/.native-image/default.properties) :

NativeImageArgs = --configurations-path /home/user/custom-image-configs \
                  -O1

If the user has a configuration file and export NATIVE_IMAGE_CONFIG_FILE=$HOME/.native-image/default.properties in ~/.bash_profile, every time the native image gets used, it will implicitly use the arguments specified as NativeImageArgs, plus the arguments specified on command line.

For a more comprehensive list of options please check the documentation on Github.

Warning: Python source code or LLVM bitcode interpreted or compiled with GraalVM Community Edition will not have the same security characteristics as the same code interpreted or compiled using GraalVM Enterprise Edition. There is a GraalVM string embedded in each image that allows to figure out the version and variant of the base (Community or Enterprise) used to build an image. The following command will query that information from an image:

strings <path to native-image exe or shared object> | grep com.oracle.svm.core.VM

Assuming you have a Java class file EmptyHello.class containing an empty main method and have generated an empty shared object emptyhello with GraalVM Native Image Generator utility of it:

$ native-image -cp hello EmptyHello
Build on Server(pid: 11228, port: 41223)
[emptyhello:11228]    classlist:     149.59 ms
...

If you do not know what GraalVM distribution is set to the PATH environment variable, how to determine if a native image was compiled with Community or Enterprise Edition? Run this command:

$ strings emptyhello | grep com.oracle.svm.core.VM

The expected output should match the following:

com.oracle.svm.core.VM GraalVM 19.2.1 CE

Profile-guided Optimizations

For additional performance gain and higher throughput in GraalVM ahead-of-time (AOT) mode, make use of profile-guided optimizations (PGO). With PGO, you can collect the profiling data in advance and then feed it to the GraalVM native-image utility, which will use this information to optimize the performance of the resulting binary.

Warning: Profile-guided optimizations (PGO) is a GraalVM Enterprise feature.

In GraalVM versions prior to 19.2.0, a commonly used technique to mitigate the missing just-in-time (JIT) optimization is to gather the execution profiles at one run and then use them to optimize subsequent compilation(s). In other words, one needs to create a native image with the --pgo-instrument option to collect the profile information. The --pgo-instrument builds an instrumented native image with profile-guided optimization data collected of AOT compiled code in the default.iprof file, if nothing else is specified. Then you run an example program, saving the result in default.iprof. Finally, you create a second native image with --pgo profile.iprof flag that should be significantly faster.

Starting from 19.2.0, you can collect profiles while running your application in JIT mode and then use this information to generate a highly-optimized native binary. This maximizes the performance even more.

  1. Run a java program in JIT mode with a -Dgraal.PGOInstrument flag to gather the profiling information:
    $ java -Dgraal.PGOInstrument=myclass.iprof MyClass
    
  2. Use the collected data to generate a native image:
    $ native-image --pgo=myclass.iprof MyClass
    
  3. Run the resulting binary:
    $ ./myclass
    

Limitations of AOT Compilation

There is a small portion of Java features are not susceptible to ahead-of-time compilation, and will therefore miss out on the performance advantages. To be able to build a highly optimized native executable, we run an aggressive static analysis that requires a closed-world assumption, which means that all classes and all bytecodes that are reachable at runtime must be known at build time. Therefore, it is not possible to load new data that have not been available during ahead-of-time compilation. A more comprehensive list of restrictions can be found in the Native Image Java Limitations document.

Tracing Agent

To overcome some restrictions of GraalVM Native Image and to simplify the configuration process, we implemented a tracing agent which records the behavior of a Java application running on GraalVM or any other compatible JVM that supports Java VM Tool Interface (JVMTI). The tracing agent is supported in both GraalVM Enterprise and Community Editions.

Warning: The tracing agent is part of the GraalVM Native Image component, which must be installed first.

The tracing agent helps to deal with such features as Reflection, Java Native Interface, Class Path Resources, and Dynamic Proxy in the GraalVM environment. It is applicable when the static analysis cannot automatically determine what to put into a native image and undetected usages of these dynamic features need to be provided to the generation process in the form of configuration files. The tracing agent observes the application behavior and builds configuration files when running on the Java HotSpot VM, thus it can be enabled on the command line with the java command:

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

Please note that -agentlib must be specified before a -jar option or a class name or any application parameters in the java command line.

During execution, the agent interfaces with the JVM to intercept all calls that look up classes, methods, fields, resources or request proxy accesses. The agent then generates the jni-config.json, reflect-config.json, proxy-config.json and resource-config.json in the specified output directory. The generated files are stand-alone configuration files in JSON format which contain all intercepted dynamic accesses.

The generated configuration files can later be supplied to the native-image tool by placing them in a META-INF/native-image/ directory on the class path, for example, in a JAR file. Not all of those files must be present. When multiple files with the same name are found, all of them are included.

Read more about advanced usage of the tracing agent in Assisted Configuration of Native Image Builds document.

Java Reflection Support

Further below we will focus on GraalVM Native Image support of Java Reflection API.

Java Reflection provides the ability to inspect and modify applications runtime behavior. It allows to inspect a class or an interface, get its methods and fields information, invoke a method, and even create an object of a class at runtime. The Reflection API classes are part of the java.lang.reflect package.

For GraalVM Native Image to handle the Reflection API, in some cases, a reflection configuration has to be provided at image build time. Within a closed-world assumption approach, an aggressive static analysis can see most classes, methods and fields with certainty because they are used directly. However, when the code accesses program elements by name via reflection, the analysis cannot always determine ahead-of-time what program elements it refers to, and the user’s assistance is then needed to make these elements accessible at runtime. For example, the analysis can discover all dynamic usages of the Class.forName("java.lang.String").getMethod("hashCode").invoke("Hello!"); sequence because only constants are used, but in String.class.getMethod("hashCode".replace('K', 'C')).invoke("Hello!"); it cannot. Where the static analysis fails to access the program elements reflectively, they must be specified in a configuration file via the option -H:ReflectionConfigurationFiles=. For more details, read our documentation on reflection support.

In the given case, the tracing agent can simplify the configuration process and write a reflection configuration file by tracing all reflective lookup operations on the Java HotSpot VM. The traced operations are Class.forName, Class.getMethod, and Class.getField.

For demonstration purposes, save the following code as ReflectionExample.java file:

import java.lang.reflect.Method;

class StringReverser {
    static String reverse(String input) {
        return new StringBuilder(input).reverse().toString();
    }
}

class StringCapitalizer {
    static String capitalize(String input) {
        return input.toUpperCase();
    }
}

public class ReflectionExample {
    public static void main(String[] args) throws ReflectiveOperationException {
        String className = args[0];
        String methodName = args[1];
        String input = args[2];

        Class<?> clazz = Class.forName(className);
        Method method = clazz.getDeclaredMethod(methodName, String.class);
        Object result = method.invoke(null, input);
        System.out.println(result);
    }
}

This is a simple Java program where non-constant strings for accessing program elements by name must come as external inputs. The main method invokes a method of a particular class (Class.forName) whose names are passed as command line arguments. Providing any other class or method name on the command line leads to an exception.

Having compiled the example, invoke each method:

$JAVA_HOME/bin/javac ReflectionExample.java
$JAVA_HOME/bin/java ReflectionExample StringReverser reverse "hello"
olleh
$JAVA_HOME/bin/java ReflectionExample StringCapitalizer capitalize "hello"
HELLO

Now we are going to build a native image as regularly, without a reflection configuration file and run a resulting image:

$JAVA_HOME/bin/native-image ReflectionExample
Build on Server(pid: 59625, port: 58819)
[reflectionexample:59625]    classlist:     467.66 ms
...
Warning: Image 'reflectionexample' is a fallback image that requires a JDK for execution (use --no-fallback to suppress fallback image generation).
$ ./reflectionexample

The reflectionexample binary is just a launcher for the Java HotSpot VM, a “fallback image” as stated in the warning message. To generate a native image with reflective lookup operations, we will apply the tracing agent to write a configuration file to be later feed into the native image generation together with a --no-fallback option.

  1. Create a directory META-INF/native-image in the working directory:
    mkdir -p META-INF/native-image
    
  2. Enable the agent and pass necessary command line arguments:
    $JAVA_HOME/bin/java -agentlib:native-image-agent=config-output-dir=META-INF/native-image ReflectionExample StringReverser reverse "hello"
    

    This command creates a reflection-config.json file which makes the StringReverser class and the reverse() method accessible via reflection. The jni-config.json, proxy-config.json ,and resource-config.json configuration files are written in that directory too.

  3. Build a native image:
    $JAVA_HOME/bin/native-image --no-fallback ReflectionExample
    

    The native image generator automatically picks up configuration files in META-INF/native-image directory or subdirectories. However, it is recommended to have META-INF/native-image location on the class path, either via a JAR file or via the -cp flag. It will help to avoid confusion for IDE users where a directory structure is defined by the tool.

  4. Test the methods, but remember that we have not run the tracing agent twice to create a configuration that supports both:
    $ ./reflectionexample StringReverser reverse "hello"
    olleh
    $ ./reflectionexample  StringCapitalizer capitalize "hello"
    Exception in thread "main" java.lang.ClassNotFoundException: StringCapitalizer
     at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:60)
     at java.lang.Class.forName(DynamicHub.java:1161)
     at ReflectionExample.main(ReflectionExample.java:21)
    

Neither the tracing agent nor native images generator cannot automatically check if the provided configuration files are complete. The agent only observes and records which values are accessed through reflection so that the same accesses are possible in a native image. You can either manually edit the reflection-config.json file, or re-run the tracing agent to transform the existing configuration file, or extend it by using config-merge-dir option:

$JAVA_HOME/bin/java -agentlib:native-image-agent=config-merge-dir=META-INF/native-image ReflectionExample StringCapitalizer capitalize "hello"

Note, the different option config-merge-dir instructs the agent to extend the existing configuration files instead of overwriting them. After re-building the native image, the StringCapitalizer class and the capitalize method will be accessible too.

Generating Heap Dumps

GraalVM also supports monitoring and generating heap dumps of the Native Image processes.

Warning: This functionality is available in the Enterprise Edition of GraalVM.

To find out more about generating heap dumps of the native image processes, refer to the step-by-step documentation.

Operational Information for Native Images

How can I specify the default amount of memory a native image can use when it gets executed?

  • To fine-tune maximum memory to be used by an image, set the percent value of physical memory using the -R:MaximumHeapSizePercent=<value> option. For more information, unfold the native-image --expert-options-all list and search for -R:MaximumHeapSizePercent=<value>, -R:MaximumYoungGenerationSizePercent=<value>, -H:AllocationBeforePhysicalMemorySize=<value> and other related options.

When does it make sense to run in a Native Image instead of the JVM?

  • When startup time, memory footprint and packaging size are important.
  • When you want to embed Java code with existing C/C++ applications.

What tools work with Native Image: debugger, profilers? How to use them?

  • The community version does not support DWARF information. The enterprise version supports all native tools that rely on DWARF information, like debuggers (GNU Project Debugger) and profilers (VTune).