Getting Started
Native Image is a technology to compile Java code ahead-of-time to a binary – a native executable. A native executable includes only the code required at run time, that is the application classes, standard-library classes, the language runtime, and statically-linked native code from the JDK.
An executable file produced by Native Image has several important advantages, in that it
- Uses a fraction of the resources required by the Java Virtual Machine, so is cheaper to run
- Starts in milliseconds
- Delivers peak performance immediately, with no warmup
- Can be packaged into a lightweight container image for fast and efficient deployment
- Presents a reduced attack surface
A native executable is created by the Native Image builder or native-image that processes your application classes and other metadata to create a binary for a specific operating system and architecture. First, the native-image tool performs static analysis of your code to determine the classes and methods that are reachable when your application runs. Second, it compiles classes, methods, and resources into a binary. This entire process is called build time to clearly distinguish it from the compilation of Java source code to bytecode.
The native-image tool can be used to build a native executable, which is the default, or a native shared library. This quick start guide focuses on building a native executable; to learn more about native shared libraries, go here.
To get used to Native Image terminology and get better understanding of the technology, we recommend you to read the Basics of Native Image.
Table of Contents
- Install Native Image
- Build a Native Executable
- Configuring Native Image with Third-Party Libraries
- License
- Further Reading
Install Native Image
Native Image can be added to GraalVM with the GraalVM Updater tool.
Run this command to install Native Image:
gu install native-image
The native-image tool is installed in the $JAVA_HOME/bin directory.
Prerequisites
The native-image tool depends on the local toolchain (header files for the C library, glibc-devel , zlib , gcc , and/or libstdc++-static ). These dependencies can be installed (if not yet installed) using a package manager on your machine. Choose your operating system to find instructions to meet the prerequisites.
// BEGIN-SNIPPET On Oracle Linux use the YUM package manager: ```shell $ sudo yum install gcc glibc-devel zlib-devel ``` Some Linux distributions may additionally require libstdc++-static. You can install libstdc++-static if the optional repositories are enabled (_ol7_optional_latest_ on Oracle Linux 7 and _ol8_codeready_builder_ on Oracle Linux 8). On Ubuntu Linux use the `apt-get` package manager: $ sudo apt-get install build-essential libz-dev zlib1g-dev On other Linux distributions use the DNF package manager: $ sudo dnf install gcc glibc-devel zlib-devel libstdc++-static // END-SNIPPET
// BEGIN-SNIPPET On macOS use Xcode: $ xcode-select --install // END-SNIPPET
// BEGIN-SNIPPET To use Native Image on Windows, install Visual Studio and Microsoft Visual C++ (MSVC). There are two installation options: * Install the Visual Studio Build Tools with the Windows SDK * Install Visual Studio with the Windows SDK You can use Visual Studio 2017 version 15.9 or later. The `native-image` builder will only work when it is run from the **x64 Native Tools Command Prompt**. The command for initiating an x64 Native Tools command prompt varies according to whether you only have the Visual Studio Build Tools installed or if you have the full Visual Studio 2019 installed. // END-SNIPPET
Build a Native Executable
The native-image tool takes Java bytecode as its input. You can build a native executable from a class file, from a JAR file, or from a module (with Java 9 and higher).
From a Class
To build a native executable from a Java class file in the current working directory, use the following command:
native-image [options] class [imagename] [options]
For example, build a native executable for a HelloWorld application.
-
Save this code into file named HelloWorld.java:
public class HelloWorld < public static void main(String[] args) < System.out.println("Hello, Native World!"); >>
javac HelloWorld.java native-image HelloWorld
./helloWorld
You can time it to see the resources used:
time -f 'Elapsed Time: %e s Max RSS: %M KB' ./helloworld # Hello, Native World! # Elapsed Time: 0.00 s Max RSS: 7620 KB
From a JAR file
To build a native executable from a JAR file in the current working directory, use the following command:
native-image [options] -jar jarfile [imagename]
The default behavior of native-image is aligned with the java command which means you can pass the -jar , -cp , -m options to build with Native Image as you would normally do with java . For example, java -jar App.jar someArgument becomes native-image -jar App.jar and ./App someArgument .
Follow this guide to build a native executable from a JAR file.
From a Module
You can also convert a modularized Java application into a native executable.
The command to build a native executable from a Java module is:
native-image [options] --module [/] [options]
For more information about how to produce a native executable from a modular Java application, see Building a HelloWorld Java Module into a Native Executable.
Build Overview
There many options you can pass to the native-image builder to configure the image build process. Run native-image —help to see the full list. The options passed to native-image are evaluated left-to-right.
For different image build tweaks and to learn more about build time configuration, see Native Image Build Configuration.
Native Image will output the progress and various statistics during the build. To learn more about the output and the different build phases, see Build Output.
Configuring Native Image with Third-Party Libraries
For more complex applications that use external libraries, you must provide the native-image builder with metadata.
Building a standalone binary with the native-image tool takes place under a “closed world assumption”. The native-image tool performs an analysis to see which classes, methods, and fields within your application are reachable and must be included in the native image. The analysis is static: it does not run your application. This means that all the bytecode in your application that can be called at run time must be known (observed and analyzed) at build time.
The analysis can determine some cases of dynamic class loading, but it cannot always exhaustively predict all usages of the Java Native Interface (JNI), Java Reflection, Dynamic Proxy objects, or class path resources. To deal with these dynamic features of Java, you inform the analysis with details of the classes that use Reflection, Proxy, and so on, or what classes to be dynamically loaded. To achieve this, you either provide the native-image tool with JSON-formatted configuration files or pre-compute metadata in the code.
To learn more about metadata, ways to provide it, and supported metadata types, see Reachability Metadata. To automatically collect metadata for your application, see Automatic Collection of Metadata.
There are also Maven and Gradle plugins for Native Image to automate building, testing and configuring native executables. Learn more here.
Lastly, not all applications may be compatible with Native Image. For more details, see Native Image Compatibility Guide.
Native Image can also interop with native languages through a custom API. Using this API, you can specify custom native entry points into your Java application and build it into a native shared library. To learn more, see Interoperability with Native Code.
License
The Native Image technology is distributed as a separate installable to GraalVM. Native Image for GraalVM Community Edition is licensed under the GPL 2 with Classpath Exception.
Native Image for GraalVM Enterprise Edition is licensed under the Oracle Technology Network License Agreement for GraalVM Enterprise Edition.
Further Reading
This getting started guide is intended for new users or those with little experience of using GraalVM Native Image. We strongly recommend these users to check the Basics of Native Image page to better understand some key aspects before going deeper.
Check user guides to become more experienced with GraalVM Native Image, find demo examples, and learn about potential usage scenarios.
For a gradual learning process, check the Native Image Build Overview and Build Configuration documentation.
Consider running interactive workshops to get some practical experience: go to Luna Labs and search for “Native Image”.
If you have stumbled across a potential bug, please submit an issue in GitHub.
If you would like to contribute to Native Image, follow our standard contributing workflow.
Native Image Basics
Native Image is written in Java and takes Java bytecode as input to produce a standalone binary (an executable, or a shared library). During the process of producing a binary, Native Image can run user code. Finally, Native Image links compiled user code, parts of the Java runtime (for example, the garbage collector, threading support), and the results of code execution into the binary.
We refer to this binary as a native executable, or a native image. We refer to the utility that produces the binary as the native-image builder, or the native-image generator.
To clearly distinguish between code executed during the native image build, and code executed during the native image execution, we refer to the difference between the two as build time and run time.
To produce a minimal image, Native Image employs a process called static analysis.
Table of Contents #
- Build Time vs Run Time
- Native Image Heap
- Static Analysis
Build Time vs Run Time #
During the image build, Native Image may execute user code. This code can have side effects, such as writing a value to a static field of a class. We say that this code is executed at build time. Values written to static fields by this code are saved in the image heap. Run time refers to code and state in the binary when it is executed.
The easiest way to see the difference between these two concepts is through configurable class initialization. In Java, a class is initialized when it is first used. Every Java class used at build time is said to be build-time initialized. Note that merely loading a class does not necessarily initialize it. The static class initializer of build-time initialized classes executes on the JVM running the image build. If a class is initialized at build time, its static fields are saved in the produced binary. At run time, using such a class for the first time does not trigger class initialization.
Users can trigger class initialization at build time in different ways:
- By passing —initialize-at-build-time= to the native-image builder.
- By using a class in the static initializer of a build-time initialized class.
Native Image will initialize frequently used JDK classes at image build time, for example, java.lang.String , java.util.** , etc. Note that build-time class initialization is an expert feature. Not all classes are suitable for build-time initialization.
The following example demonstrates the difference between build-time and run-time executed code:
public class HelloWorld < static class Greeter < static < System.out.println("Greeter is getting ready!"); >public static void greet() < System.out.println("Hello, World!"); >> public static void main(String[] args) < Greeter.greet(); >>
Having saved the code in a file named HelloWorld.java, we compile and run the application on the JVM:
javac HelloWorld.java java HelloWorld Greeter is getting ready! Hello, World!
Now we build a native image of it, and then execute:
native-image HelloWorld ======================================================================================================================== GraalVM Native Image: Generating 'helloworld' (executable). ======================================================================================================================== . Finished generating 'helloworld' in 14.9s.
./helloworld Greeter is getting ready! Hello, World!
HelloWorld started up and invoked Greeter.greet . This caused Greeter to initialize, printing the message Greeter is getting ready! . Here we say the class initializer of Greeter is executed at image run time.
What would happen if we tell native-image to initialize Greeter at build time?
native-image HelloWorld --initialize-at-build-time=HelloWorld\$Greeter ======================================================================================================================== GraalVM Native Image: Generating 'helloworld' (executable). ======================================================================================================================== Greeter is getting ready! [1/7] Initializing. (3.1s @ 0.15GB) Version info: 'GraalVM dev Java 11 EE' Java version info: '11.0.15+4-jvmci-22.1-b02' C compiler: gcc (linux, x86_64, 9.4.0) Garbage collector: Serial GC . Finished generating 'helloworld' in 13.6s. ./helloworld Hello, World!
We saw Greeter is getting ready! printed during the image build. We say the class initializer of Greeter executed at image build time. At run time, when HelloWorld invoked Greeter.greet , Greeter was already initialized. The static fields of classes initialized during the image build are stored in the image heap.
Native Image Heap #
The Native Image heap, also called the image heap, contains:
- Objects created during the image build that are reachable from application code.
- java.lang.Class objects of classes used in the native image.
- Object constants embedded in method code.
When native image starts up, it copies the initial image heap from the binary.
One way to include objects in the image heap is to initialize classes at build time:
class Example < private static final String message; static < message = System.getProperty("message"); >public static void main(String[] args) < System.out.println("Hello, World! My message is: " + message); >>
Now we compile and run the application on the JVM:
javac Example.java java -Dmessage=hi Example Hello, World! My message is: hi
java -Dmessage=hello Example Hello, World! My message is: hello
java Example Hello, World! My message is: null
Now examine what happens when we build a native image in which the Example class is initialized at build time:
native-image Example --initialize-at-build-time=Example -Dmessage=native ================================================================================ GraalVM Native Image: Generating 'example' (executable). ================================================================================ . Finished generating 'example' in 19.0s.
./example Hello, World! My message is: native
./example -Dmessage=aNewMessage Hello, World! My message is: native
The class initializer of the Example class was executed at image build time. This created a String object for the message field and stored it inside the image heap.
Static Analysis #
Static analysis is a process that determines which program elements (classes, methods and fields) are used by an application. These elements are also referred to as reachable code. The analysis itself has two parts:
- Scanning the bytecodes of a method to determine what other elements are reachable from it.
- Scanning the root objects in the native image heap (i.e., static fields) to determine which classes are reachable from them. It starts from the entry points of the application (i.e., the main method). The newly discovered elements are iteratively scanned until further scanning yields no additional changes in element’s reachability.
Only reachable elements are included in the final image. Once a native image is built, no new elements can be added at run time, for example, through class loading. We refer to this constraint as the closed-world assumption.
Further Reading #
- Native Image Build Overview
- Class Initialization in Native Image
При подготовке материала использовались источники:
https://docs.oracle.com/en/graalvm/enterprise/22/docs/reference-manual/native-image/
https://www.graalvm.org/latest/reference-manual/native-image/basics/