- Latest (GraalVM for JDK 21)
- Dev Build
- GraalVM for JDK 21
- GraalVM for JDK 20
- GraalVM for JDK 17
- GraalVM 22.3
- GraalVM 22.2
- GraalVM 22.1
- GraalVM 22.0
- GraalVM 21.3
- Native Image
- How-to Guides
- Native Image Basics
- Build Overview
- Reachability Metadata
- Compatibility Guide
- Collect Metadata with the Tracing Agent
- Experimental Agent Options
- Optimizations and Performance
- Debugging and Diagnostics
- Dynamic Features
- Interoperability with Native Code
- LLVM Backend
- Workshops and Labs
Native Image Compatibility Guide
Native Image uses a different way of compiling a Java application than the traditional Java virtual machine (VM).
It distinguishes between build time and run time.
At the image build time, the
native-image builder performs static analysis to find all the methods that are reachable from the entry point of an application.
The builder then compiles these (and only these) methods into an executable binary.
Because of this different compilation model, a Java application can behave somewhat differently when compiled into a native image.
Native Image provides an optimization to reduce the memory footprint and startup time of an application.
This approach relies on a “closed-world assumption” in which all code is known at build time. That is, no new code is loaded at run time.
As with most optimizations, not all applications are amenable to this approach.
native-image builder is unable to optimize an application at build time, it generates a so-called “fallback file” that requires a Java VM to run.
We recommend to check Native Image Basics for a detailed description what happens with your Java application at build and run times.
Features Requiring Metadata #
To be suitable for closed-world assumption, the following Java features generally require metadata to pass to
native-image at build time.
This metadata ensures that a native image uses the minimum amount of space necessary.
The compatibility of Native Image with the most popular Java libraries was recently enhanced by publishing shared reachability metadata on GitHub. The users can share the burden of maintaining metadata for third-party dependencies and reuse it. See Reachability Metadata to learn more.
Features Incompatible with Closed-World Assumption #
Some Java features are not yet supported within the closed-world assumption, and if used, result in a fallback file.
invokedynamic Bytecode and Method Handles #
Under the closed-world assumption, all methods that are called and their call sites must be known.
invokedynamicmethod and method handles can introduce calls at run time or change the method that is invoked.
invokedynamic use cases generated by
javac for, for example, Java lambda expressions and String concatenation that are supported because they do not change called methods at run time.
Security Manager #
The Java security manager is no longer recommended as a way to isolate less trusted code from more trusted code in the same process. This is because almost all typical hardware architectures are susceptible to side-channel attacks to access data that is restricted via the security manager. Using separate processes is now recommended for these cases.
Features That May Operate Differently in a Native Image #
Native Image implements some Java features differently to the Java VM.
Signal Handlers #
Registering a signal handler requires a new thread to start that handles the signal and invokes shutdown hooks. By default, no signal handlers are registered when building a native image, unless they are registered explicitly by the user. For example, it is not recommended to register the default signal handlers when building a shared library, but it is desirable to include signal handlers when building a native executable for containerized environments, such as Docker containers.
To register the default signal handlers, pass the
--install-exit-handlers option to the
This option gives you the same signal handlers as a Java VM.
Class Initializers #
By default, classes are initialized at run time.
This ensures compatibility, but limits some optimizations.
For faster startup and better peak performance, it is better to initialize classes at build time.
Class initialization behavior can be specified using the options
--initialize-at-run-time for specific classes and packages or for all classes.
Classes that are members of the JDK class libraries are initialized by default.
Note: Class initialization at build time may break specific assumptions in existing code.
For example, files loaded in a class initializer may not be in the same place at build time as at run time.
Also, certain objects such as a file descriptors or running threads must not be stored in a native executable.
If such objects are reachable at build time, the
native image builder fails with an error.
For more information, see Class Initialization in Native Image.
The Java base class
java.lang.Object defines the method
It is called by the garbage collector on an object when garbage collection determines that there are no more references to the object.
A subclass can override the
finalize() method to dispose of system resources or to perform other cleanup operations.
Finalizers have been deprecated since Java SE 9. They are complicated to implement, and have badly designed semantics. For example, a finalizer can cause an object to be reachable again by storing a reference to it in a static field. Therefore, finalizers are not invoked. We recommend you replace finalizers with weak references and reference queues.
Native Image does not implement long-deprecated methods in
java.lang.Thread such as
Unsafe Memory Access #
Fields that are accessed using
sun.misc.Unsafe need to be marked as such for the static analysis if classes are initialized at build time.
In most cases, that happens automatically: field offsets stored in
static final fields are automatically rewritten from the hosted value (the field offset for the Java VM on which the
native image builder is running) to the native executable value, and as part of that rewrite the field is marked as
For non-standard patterns, field offsets can be recomputed manually using the annotation
Debugging and Monitoring #
Java has some optional specifications that a Java implementation can use for debugging and monitoring Java programs, including JVMTI.
They help you monitor the Java VM at runtime for events such as compilation, for example, which do not occur in most native images.
These interfaces are built on the assumption that Java bytecodes are available at run time, which is not the case for native images built with the closed-world optimization.
native-image builder generates a native executable, users must use native debuggers and monitoring tools (such as GDB or VTune) rather than tools targeted for Java.
JVMTI and other bytecode-based tools are not supported with Native Image.
Limitations on Linux ARM64 Architecture
Mostly all Native Image features are supported on Linux ARM 64-bit architecture, except for the limitations described below.
-R:[+|-]WriteableCodeCache: must be disabled.
muslis not supported.
--gc=<value>: The G1 garbage collector (
G1) is not supported.
Find a complete list of options to the
native-image builder here.