Native Image Build Output

Here you will find information about the build output of GraalVM Native Image. Below is the example output when building a native executable of the HelloWorld class:

================================================================================
GraalVM Native Image: Generating 'helloworld' (executable)...
================================================================================
[1/8] Initializing...                                            (2.8s @ 0.15GB)
 Java version: 20+34, vendor version: GraalVM CE 20-dev+34.1
 Graal compiler: optimization level: 2, target machine: x86-64-v3
 C compiler: gcc (linux, x86_64, 12.2.0)
 Garbage collector: Serial GC (max heap size: 80% of RAM)
--------------------------------------------------------------------------------
 Build resources:
 - 13.24GB of memory (42.7% of 31.00GB system memory, determined at start)
 - 16 thread(s) (100.0% of 16 available processor(s), determined at start)
[2/8] Performing analysis...  [****]                             (4.5s @ 0.54GB)
    3,163 reachable types   (72.5% of    4,364 total)
    3,801 reachable fields  (50.3% of    7,553 total)
   15,183 reachable methods (45.5% of   33,405 total)
      957 types,    81 fields, and   480 methods registered for reflection
       57 types,    55 fields, and    52 methods registered for JNI access
        4 native libraries: dl, pthread, rt, z
[3/8] Building universe...                                       (0.8s @ 0.99GB)
[4/8] Parsing methods...      [*]                                (0.6s @ 0.75GB)
[5/8] Inlining methods...     [***]                              (0.3s @ 0.32GB)
[6/8] Compiling methods...    [**]                               (3.7s @ 0.60GB)
[7/8] Laying out methods...   [*]                                (0.8s @ 0.83GB)
[8/8] Creating image...       [**]                               (3.1s @ 0.58GB)
   5.32MB (24.22%) for code area:     8,702 compilation units
   7.03MB (32.02%) for image heap:   93,301 objects and 5 resources
   8.96MB (40.83%) for debug info generated in 1.0s
 659.13kB ( 2.93%) for other data
  21.96MB in total
--------------------------------------------------------------------------------
Top 10 origins of code area:            Top 10 object types in image heap:
   4.03MB java.base                        1.14MB byte[] for code metadata
 927.05kB svm.jar (Native Image)         927.31kB java.lang.String
 111.71kB java.logging                   839.68kB byte[] for general heap data
  63.38kB org.graalvm.nativeimage.base   736.91kB java.lang.Class
  47.59kB jdk.proxy1                     713.13kB byte[] for java.lang.String
  35.85kB jdk.proxy3                     272.85kB c.o.s.c.h.DynamicHubCompanion
  27.06kB jdk.internal.vm.ci             250.83kB java.util.HashMap$Node
  23.44kB org.graalvm.sdk                196.52kB java.lang.Object[]
  11.42kB jdk.proxy2                     182.77kB java.lang.String[]
   8.07kB jdk.graal.compiler             154.26kB byte[] for embedded resources
   1.39kB for 2 more packages              1.38MB for 884 more object types
--------------------------------------------------------------------------------
Recommendations:
 HEAP: Set max heap for improved and more predictable memory usage.
 CPU:  Enable more CPU features with '-march=native' for improved performance.
--------------------------------------------------------------------------------
    0.8s (4.6% of total time) in 35 GCs | Peak RSS: 1.93GB | CPU load: 9.61
--------------------------------------------------------------------------------
Build artifacts:
 /home/janedoe/helloworld/helloworld (executable)
 /home/janedoe/helloworld/helloworld.debug (debug_info)
 /home/janedoe/helloworld/sources (debug_info)
================================================================================
Finished generating 'helloworld' in 17.0s.

Build Stages #

Initializing #

In this stage, the Native Image build process is set up and Features are initialized.

Native Image Kind

By default, Native Image generates executables but it can also generate native shared libraries and static executables.

Java Version Info

The Java and vendor version of the Native Image process. Both are also used for the java.vm.version and java.vendor.version properties within the generated native binary. Please report version and vendor when you file issues.

Graal Compiler

The selected optimization level and targeted machine type used by the Graal compiler. The optimization level can be controlled with the -O option and defaults to 2, which enables aggressive optimizations. Use -Ob to enable quick build mode, which speeds up the compilation stage. This is useful during development, or when peak throughput is less important and you would like to optimize for size. The targeted machine type can be selected with the -march option and defaults to x86-64-v3 on AMD64 and armv8-a on AArch64. See here for recommendations on how to use this option.

On Oracle GraalVM, the line also shows information about Profile-Guided Optimizations (PGO).

  • off: PGO is not used
  • instrument: The generated executable or shared library is instrumented to collect data for PGO (--pgo-instrument)
  • user-provided: PGO is enabled and uses a user-provided profile (for example --pgo default.iprof)
  • ML-inferred: A machine learning (ML) model is used to infer profiles for control split branches statically.

C Compiler

The C compiler executable, vendor, target architecture, and version info used by the Native Image build process.

Garbage Collector

The garbage collector used within the generated executable:

  • The Serial GC is the default GC and optimized for low memory footprint and small Java heap sizes.
  • The G1 GC (not available in GraalVM Community Edition) is a multithreaded GC that is optimized to reduce stop-the-world pauses and therefore improve latency while achieving high throughput.
  • The Epsilon GC does not perform any garbage collection and is designed for very short-running applications that only allocate a small amount of memory.

For more information see the docs on Memory Management.

Maximum Heap Size

By default, the heap size is limited to a certain percentage of your system memory, allowing the garbage collector to freely allocate memory according to its policy. Use the -Xmx option when invoking your native executable (for example ./myapp -Xmx64m for 64MB) to limit the maximum heap size for a lower and more predictable memory footprint. This can also improve latency in some cases. Use the -R:MaxHeapSize option when building with Native Image to preconfigure the maximum heap size.

User-Specific Features

All Features that are either provided or specifically enabled by the user, or implicitly registered for the user, for example, by a framework. GraalVM Native Image deploys a number of internal features, which are excluded from this list.

Experimental Options

A list of all active experimental options, including their origin and possible API option alternatives if available.

Using experimental options should be avoided in production and can change in any release. If you rely on experimental features and would like an option to be considered stable, please file an issue.

Picked up NATIVE_IMAGE_OPTIONS

Additional build options picked up via the NATIVE_IMAGE_OPTIONS environment variable. Similar to JAVA_TOOL_OPTIONS, the value of the environment variable is prefixed to the options supplied to native-image. Argument files are not allowed to be passed via NATIVE_IMAGE_OPTIONS. The NATIVE_IMAGE_OPTIONS environment variable is designed to be used by users, build environments, or tools to inject additional build options.

Build Resources

The memory limit and number of threads used by the build process.

More precisely, the memory limit of the Java heap, so actual memory consumption can be even higher. Please check the peak RSS reported at the end of the build to understand how much memory was actually used. By default, the build process tries to only use free memory (to avoid memory pressure on the build machine), and never more than 32GB of memory. If less than 8GB of memory are free, the build process falls back to use 85% of total memory. Therefore, consider freeing up memory if your machine is slow during a build, for example, by closing applications that you do not need. It is possible to overwrite the default behavior, for example with -J-XX:MaxRAMPercentage=60.0 or -J-Xmx16g.

By default, the build process uses all available processors to maximize speed, but not more than 32 threads. Use the --parallelism option to set the number of threads explicitly (for example, --parallelism=4). Use fewer threads to reduce load on your system as well as memory consumption (at the cost of a slower build process).

Performing Analysis #

In this stage, a points-to analysis is performed. The progress indicator visualizes the number of analysis iterations. A large number of iterations can indicate problems in the analysis likely caused by misconfiguration or a misbehaving feature.

Reachable Types, Fields, and Methods

The number of types (primitives, classes, interfaces, and arrays), fields, and methods that are reachable versus the total number of types, fields, and methods loaded as part of the build process. A significantly larger number of loaded elements that are not reachable can indicate a configuration problem. To reduce overhead, please ensure that your class path and module path only contain entries that are needed for building the application.

Reflection Registrations

The number of types, fields, and methods that are registered for reflection. Large numbers can cause significant reflection overheads, slow down the build process, and increase the size of the native binary (see reflection metadata).

JNI Access Registrations

The number of types, fields, and methods that are registered for JNI access.

Foreign functions stubs

The number of downcalls and upcalls registered for foreign function access.

Runtime Compiled Methods

The number of methods marked for runtime compilation. This number is only shown if runtime compilation is built into the executable, for example, when building a Truffle language. Runtime-compiled methods account for graph encodings in the heap.

Building Universe #

In this stage, a universe with all types, fields, and methods is built, which is then used to create the native binary.

Parsing Methods #

In this stage, the Graal compiler parses all reachable methods. The progress indicator is printed periodically at an increasing interval.

Inlining Methods #

In this stage, trivial method inlining is performed. The progress indicator visualizes the number of inlining iterations.

Compiling Methods #

In this stage, the Graal compiler compiles all reachable methods to machine code. The progress indicator is printed periodically at an increasing interval.

Laying Out Methods #

In this stage, compiled methods are laid out. The progress indicator is printed periodically at an increasing interval.

Creating Image #

In this stage, the native binary is created and written to disk. Debug info is also generated as part of this stage (if requested).

Code Area

The code area contains machine code produced by the Graal compiler for all reachable methods. Therefore, reducing the number of reachable methods also reduces the size of the code area.

Origins of Code Area

To help users understand where the machine code of the code area comes from, the build output shows a breakdown of the top origins. An origin is a group of Java sources and can be a JAR file, a package name, or a class name, depending on the information available. The java.base module, for example, contains base classes from the JDK. The svm.jar file, the org.graalvm.nativeimage.base module, and similar origins contain internal sources for the Native Image runtime. To reduce the size of the code area and with that, the total size of the native executable, re-evaluate the dependencies of your application based on the code area breakdown. Some libraries and frameworks are better prepared for Native Image than others, and newer versions of a library or framework may improve (or worsen) their code footprint.

Image Heap

The heap contains reachable objects such as static application data, metadata, and byte[] for different purposes (see below).

General Heap Data Stored in byte[]

The total size of all byte[] objects that are neither used for java.lang.String, nor code metadata, nor reflection metadata, nor graph encodings. Therefore, this can also include byte[] objects from application code.

Embedded Resources Stored in byte[]

The total size of all byte[] objects used for storing resources (for example, files accessed via Class.getResource()) within the native binary. The number of resources is shown in the Heap section. A list of all resources including additional information such as their module, name, origin, and size are included in the build reports. This information can also be requested in the JSON format using the -H:+GenerateEmbeddedResourcesFile option. Such a JSON file validates against the JSON schema defined in embedded-resources-schema-v1.0.0.json.

Code Metadata Stored in byte[]

The total size of all byte[] objects used for metadata for the code area. Therefore, reducing the number of reachable methods also reduces the size of this metadata.

Reflection Metadata Stored in byte[]

The total size of all byte[] objects used for reflection metadata, including types, field, method, and constructor data. To reduce the amount of reflection metadata, reduce the number of elements registered for reflection.

Graph Encodings Stored in byte[]

The total size of all byte[] objects used for graph encodings. These encodings are a result of runtime compiled methods. Therefore, reducing the number of such methods also reduces the size of corresponding graph encodings.

Heap Alignment

Additional space reserved to align the heap for the selected garbage collector. The heap alignment may also contain GC-specific data structures. Its size can therefore only be influenced by switching to a different garbage collector.

Debug Info

The total size of generated debug information (if enabled).

Other Data

The amount of data in the binary that is neither in the code area, nor in the heap, nor debug info. This data typically contains internal information for Native Image and should not be dominating.

Security Report #

This section is not available in GraalVM Community Edition.

Deserialization

This shows whether Java deserialization is included in the native executable or not. If not included, the attack surface of the executable is reduced as the executable cannot be exploited with attacks based on Java deserialization.

Embedded SBOM

Number of components and the size of the embedded Software Bill of Materials (SBOM). Use --enable-sbom to include an SBOM in the native executable. For more information, see Inspection Tool

Backwards-Edge Control-Flow Integrity (CFI)

Control-Flow Integrity (CFI) can be enforced with the experimental -H:CFI=HW option. This feature is currently only available for code compiled by Graal for Linux AArch64 and leverages pointer authentication codes (PAC) to ensure integrity of a function’s return address.

Software Control-Flow Integrity (CFI)

Control-Flow Integrity (CFI) can be enforced in software with the experimental -H:CFI=SW_NONATIVE option. This feature is currently only available for code compiled by Graal for Linux AMD64 and validates targets of indirect branches and method returns.

Recommendations #

The build output may contain one or more of the following recommendations that help you get the best out of Native Image.

INIT: Use the Strict Image Heap Configuration

Start using --strict-image-heap to reduce the amount of configuration and prepare for future GraalVM releases where this will be the default. This mode requires only the classes that are stored in the image heap to be marked with --initialize-at-build-time. This effectively reduces the number of configuration entries necessary to achieve build-time initialization. When adopting the new mode it is best to start introducing build-time initialization from scratch. During this process, it is best to select individual classes (as opposed to whole packages) for build time initialization. Also, before migrating to the new flag make sure to update all framework dependencies to the latest versions as they might need to migrate too.

AWT: Missing Reachability Metadata for Abstract Window Toolkit

The Native Image analysis has included classes from the java.awt package but could not find any reachability metadata for it. Use the tracing agent to collect such metadata for your application. Otherwise, your application is unlikely to work properly. If your application is not a desktop application (for example using Swing or AWT directly), you may want to re-evaluate whether the dependency on AWT is actually needed.

CPU: Enable More CPU Features for Improved Performance

The Native Image build process has determined that your CPU supports more features, such as AES or LSE, than currently enabled. If you deploy your application on the same machine or a similar machine with support for the same CPU features, consider using -march=native at build time. This option allows the Graal compiler to use all CPU features available, which in turn can significantly improve the performance of your application. Use -march=list to list all available machine types that can be targeted explicitly.

G1GC: Use G1 Garbage Collector for Improved Latency and Throughput

The G1 garbage collector is available for your platform. Consider enabling it using --gc=G1 at build time to improve the latency and throughput of your application. For more information see the docs on Memory Management. For best peak performance, also consider using Profile-Guided Optimizations.

HEAP: Specify a Maximum Heap Size

Please refer to Maximum Heap Size.

PGO: Use Profile-Guided Optimizations for Improved Throughput

Consider using Profile-Guided Optimizations to optimize your application for improved throughput. These optimizations allow the Graal compiler to leverage profiling information, similar to when it is running as a JIT compiler, when AOT-compiling your application. For this, perform the following steps:

  1. Build your application with --pgo-instrument.
  2. Run your instrumented application with a representative workload to generate profiling information in the form of an .iprof file.
  3. Re-build your application and pass in the profiling information with --pgo=<your>.iprof to generate an optimized version of your application.

Relevant guide: Optimize a Native Executable with Profile-Guided Optimizations.

For best peak performance, also consider using the G1 garbage collector.

QBM: Use Quick Build Mode for Faster Builds

Consider using the quick build mode (-Ob) to speed up your builds during development. More precisely, this mode reduces the number of optimizations performed by the Graal compiler and thus reduces the overall time of the compilation stage. The quick build mode is not only useful for development, it can also cause the generated executable file to be smaller in size. Note, however, that the overall peak throughput of the executable may be lower due to the reduced number of optimizations.

Resource Usage Statistics #

Garbage Collections

The total time spent in all garbage collectors, total GC time divided by the total process time as a percentage, and the total number of garbage collections. A large number of collections or time spent in collectors usually indicates that the system is under memory pressure. Increase the amount of available memory to reduce the time to build the native binary.

Peak RSS

Peak resident set size as reported by the operating system. This value indicates the maximum amount of memory consumed by the build process. You may want to compare this value to the memory limit reported in the build resources section. If there is enough headroom and the GC statistics do not show any problems, the amount of total memory of the system can be reduced to a value closer to the peak RSS to lower operational costs.

CPU load

The CPU time used by the process divided by the total process time. Increase the number of CPU cores to reduce the time to build the native binary.

Build Artifacts #

The list of all build artifacts. This includes the generated native binary, but it can also contain other artifacts such as additional libraries, C header files, or debug info. Some of these artifacts must remain in the same location with the native binary as they are needed at run time. For applications using AWT, for example, the build process will also output libraries from the JDK and shims to provide compatible AWT support. These libraries need to be copied and distributed together with the native binary. Use the -H:+GenerateBuildArtifactsFile option to instruct the builder to produce a machine-readable version of the build artifact list in JSON format. Such a JSON file validates against the JSON schema defined in build-artifacts-schema-v0.9.0.json. This schema also contains descriptions for each possible artifact type and explains whether they are needed at run time or not.

Machine-Readable Build Output #

The build output produced by the native-image builder is designed for humans, can evolve with new releases, and should thus not be parsed in any way by tools. Instead, use the -H:BuildOutputJSONFile=<file.json> option to instruct the builder to produce machine-readable build output in JSON format that can be used, for example, for building monitoring tools. Such a JSON file validates against the JSON schema defined in build-output-schema-v0.9.3.json. Note that a JSON file is produced if and only if a build succeeds.

The following example illustrates how this could be used in a CI/CD build pipeline to check that the number of reachable methods does not exceed a certain threshold:

native-image -H:BuildOutputJSONFile=build.json HelloWorld
# ...
cat build.json | python3 -c "import json,sys;c = json.load(sys.stdin)['analysis_results']['methods']['reachable']; assert c < 12000, f'Too many reachable methods: {c}'"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
AssertionError: Too many reachable methods: 12128

Colorful Build Output #

By default, the native-image builder colors the build output for better readability when it finds an appropriate terminal. It also honors the NO_COLOR, CI, and TERM environment variables when checking for color support. To explicitly control colorful output, set the --color option to always, never, or auto (default).

Connect with us