This article describes the approach before the GraalVM 19.0 release. A newer version covering GraalVM 19.0 and later is available here: https://medium.com/graalvm/updates-on-class-initialization-in-graalvm-native-image-generation-c61faca461f7

tl;dr: Classes reachable for a GraalVM native image are initialized at image build time. Objects allocated by class initializers are in the image heap that is part of the executable. The new option --delay-class-initialization-to-runtime= delays initialization of listed classes to image run time.

The GraalVM native-image tool enables ahead-of-time (AOT) compilation of Java applications into native executables or shared libraries. While traditionally Java code is just-in-time (JIT) compiled at run time, AOT compilation has two main advantages: First, it improves the start-up time since the code is already pre-compiled into efficient machine code. Second, it reduces the memory footprint of Java applications since it eliminates the need to include infrastructure to load and optimize code at run time.

We call the technology behind the GraalVM native-image tool Substrate Virtual Machine (VM) because, in addition to your application code and its dependencies, the target executable contains traditional VM components such as a garbage collector. To achieve the goals of a self-contained executable, the native-image tool performs a static analysis to find ahead-of-time the code that your application uses. This includes the parts of the JDK that your application uses, third-party library code, and the VM code itself (which is written in Java too). The following figure summarizes the native image build process:

The native executable contains not just code, but also an initial heap that serves as the starting point of the Java heap at run time. We call this initial heap the “image heap”. The image heap allows us to skip class initialization at run time, which is crucial for fast startup. While a traditional Java VM needs to run the class initializers of many core JDK classes before the main method of your application starts running, the native executable calls your main method quite directly.

A class initializer can be written explicitly as a static { ... } block in Java code. But every initialization of a static field (regardless whether the field is final or not) is implicitly converted to a class initializer. For example, when you declared static field as static final Date TIME = new Date(); then the assignment TIME = new Date() is in the class initializer. We will use this example later. But let’s first start with the minimal “Hello World” for class initialization:

To set up your development environment you first need to download the latest GraalVM. Either the Community Edition or the Enterprise Edition works for the purpose of this example. The GraalVM download contains a full JDK plus some other utilities like the native-image tool. Then you need to set your JAVA_HOME to point to the GraalVM directory. Now we can compile the example:

> $JAVA_HOME/bin/javac HelloClassInitializer.java

When we run it on a normal Java VM, it first prints the line from the class initializer and then the line from the main method:

> $JAVA_HOME/bin/java HelloClassInitializer

Hello Class Initializer

Hello Main

In order to get a standalone native executable for our application, we use the native-image tool that is part of the GraalVM download, and then run the helloclassinitializer executable that it creates:

> $JAVA_HOME/bin/native-image HelloClassInitializer

Build on Server(pid: 23652, port: 40485)

[helloclassinitializer:23652] classlist: 388.19 ms

[helloclassinitializer:23652] (cap): 650.59 ms

[helloclassinitializer:23652] setup: 1,008.77 ms

Hello Class Initializer

[helloclassinitializer:23652] (typeflow): 2,945.18 ms

[helloclassinitializer:23652] (objects): 1,666.47 ms

[helloclassinitializer:23652] (features): 37.61 ms

[helloclassinitializer:23652] analysis: 4,714.60 ms

[helloclassinitializer:23652] universe: 129.10 ms

[helloclassinitializer:23652] (parse): 512.04 ms

[helloclassinitializer:23652] (inline): 827.93 ms

[helloclassinitializer:23652] (compile): 4,344.61 ms

[helloclassinitializer:23652] compile: 5,974.13 ms

[helloclassinitializer:23652] image: 488.75 ms

[helloclassinitializer:23652] write: 107.55 ms

[helloclassinitializer:23652] [total]: 12,846.35 ms > ./helloclassinitializer

Hello Main

Note that the line “Hello Class Initializer” is printed during image generation. Executing the binary only prints the line “Hello Main”. Running the helloclassinitializer executable always prints only the one line. This is the intended behavior: In order to have a fast startup of the executable, the (possibly expensive) class initializer is run only once during image generation.