Skip to main content

Class Initialization

One of the important factors that slow down the startup of Java apps is class initialization. All classes need to be loaded and initialized. For large applications, the amount of time spent on class initialization can be huge.

Class Initialization Strategy

Native Image supports class initialization at the image build time, so it's unnecessary to initialize these classes at the runtime. All the static state information from build time initialization is stored in the native image.

Native Image tries to initialize as many classes as possible at the build time. Most of the Native Image runtime classes are initialized at the image build time, which include garbage collectors, JDK classes, the deoptimizer, etc. For application classes, Native Image tries to find classes that are safe to initialize at build time.

Class initialization policy can be specified for packages and individual classes. --initialize-at-build-time and --initialize-at-run-time options can be used.

Class Initialization and Inheritance

If a class is initialized at image build time, then all its superclasses are also initialized at image build time.

If a class is initialized at runtime, then all its subclasses are also initialized at runtime.

The code below is a simple game to guess the number. The target number is a random number. Because the target variable is static, its value is assigned when the class is initialized. In JVM mode, the value of target will be re-initialized when the program runs. So it's unlikely that you can guess the correct number.

When building a native image, if the GuessNumber class is initialized at image build time, the value of target will only be initialized once, and the value is recorded in the image.

Guess number
package io.vividcode.graalvm.initialization;

import java.util.concurrent.ThreadLocalRandom;

public class GuessNumber {

private static final int target = ThreadLocalRandom.current().nextInt(100);

public static void main(String[] args) {
if (args.length == 0) {
System.err.println("A number is required!");
return;
}
try {
int guess = Integer.parseInt(args[0]);
if (guess > target) {
System.out.println("Too big!");
} else if (guess < target) {
System.out.println("Too small!");
} else {
System.out.println("You win!");
}
} catch (NumberFormatException e) {
System.err.println("Invalid number input!");
}
}
}

When the native image is created, we can run the executable multiple times to eventually guess the correct number.

native-image --initialize-at-build-time=io.vividcode.graalvm.initialization.GuessNumber \
io.vividcode.graalvm.initialization.GuessNumber guess-number

There are three kinds of initialization, which are enum values of com.oracle.svm.hosted.classinitialization.InitKind.

KindTimeDescription
BUILD_TIMEBuild timeClass is initialized during image building, so it is already initialized at runtime.
RERUNBothClass is initialized both at runtime and during image building.
RUN_TIMERun timeClass should be initialized at runtime and not during image building.

Debug Class Initialization

If there are problems related to class initialization, the -H:+PrintClassInitialization option can be used to show details about class initialization. native-image prints class initialization configuration, dependencies, and reports to local files.

When enabled, three files will be generated in the reports directory. For each class or package, the report shows the initialization kind and reason. There is also a dot file shows the dependencies of class initializers.

After adding the -H:+PrintClassInitialization option to native-image, three files are generated in the reports directory.

reports
├── class_initialization_configuration_20220212_221948.csv
├── class_initialization_dependencies_20220212_222004.dot
└── class_initialization_report_20220212_222004.csv

The configuration CSV file shows the configurations of class initialization. The --initialize-at-build-time and --initialize-at-run-time options affect the configurations.

Class or Package Name, Initialization Kind, Reasons
boolean, BUILD_TIME, primitive types are initialized at build time
byte, BUILD_TIME, primitive types are initialized at build time
byte[], BUILD_TIME, arrays are initialized at build time
byte[][], BUILD_TIME, arrays are initialized at build time
char, BUILD_TIME, primitive types are initialized at build time
com.oracle.graal, BUILD_TIME, Native Image classes are always initialized at build time

The report CSV file shows the actual result of class initialization. There are more rows in this file than the configuration file.

The dependencies DOT file shows the dependencies between classes.

Common Errors

There are some common errors related to class initialization.

Wrong Initialization Stage

This error happens when classes that should be initialized at runtime got initialized during image building. The error message looks like below. It shows class initialization issues related to Netty.

From the message, we can know that the io.netty.util.AbstractReferenceCounted class was requested to be initialized at runtime by the native-image.properties configuration file in the netty-common-4.1.49.Final.jar file.

Error message
com.oracle.svm.core.util.UserError$UserException: Classes that should be initialized at run time got initialized during image building:
io.netty.buffer.UnpooledHeapByteBuf the class was requested to be initialized at run time (subtype of io.netty.buffer.AbstractReferenceCountedByteBuf). To see why io.netty.buffer.UnpooledHeapByteBuf got initialized use --trace-class-initialization=io.netty.buffer.UnpooledHeapByteBuf
io.netty.util.AbstractReferenceCounted the class was requested to io.netty.util.AbstractReferenceCounted (from jar:file:///Users/alexcheng/.m2/repository/io/netty/netty-common/4.1.49.Final/netty-common-4.1.49.Final.jar!/META-INF/native-image/io.netty/common/native-image.properties with 'io.netty.util.AbstractReferenceCounted'). To see why io.netty.util.AbstractReferenceCounted got initialized use --trace-class-initialization=io.netty.util.AbstractReferenceCounted
io.netty.buffer.AbstractPooledDerivedByteBuf the class was requested to be initialized at run time (subtype of io.netty.buffer.AbstractReferenceCountedByteBuf). To see why io.netty.buffer.AbstractPooledDerivedByteBuf got initialized use --trace-class-initialization=io.netty.buffer.AbstractPooledDerivedByteBuf
io.netty.buffer.UnpooledDirectByteBuf the class was requested to be initialized at run time (subtype of io.netty.buffer.AbstractReferenceCountedByteBuf). To see why io.netty.buffer.UnpooledDirectByteBuf got initialized use --trace-class-initialization=io.netty.buffer.UnpooledDirectByteBuf
io.netty.buffer.ByteBufUtil the class was requested to be initialized at run time (from jar:file:///Users/alexcheng/.m2/repository/io/netty/netty-buffer/4.1.49.Final/netty-buffer-4.1.49.Final.jar!/META-INF/native-image/io.netty/buffer/native-image.properties with 'io.netty.buffer.ByteBufUtil'). To see why io.netty.buffer.ByteBufUtil got initialized use --trace-class-initialization=io.netty.buffer.ByteBufUtil
io.netty.buffer.ByteBufAllocator the class was requested to be initialized at run time (from jar:file:///Users/alexcheng/.m2/repository/io/netty/netty-buffer/4.1.49.Final/netty-buffer-4.1.49.Final.jar!/META-INF/native-image/io.netty/buffer/native-image.properties with 'io.netty.buffer.ByteBufAllocator'). To see why io.netty.buffer.ByteBufAllocator got initialized use --trace-class-initialization=io.netty.buffer.ByteBufAllocator
io.netty.buffer.UnpooledUnsafeDirectByteBuf the class was requested to be initialized at run time (subtype of io.netty.buffer.AbstractReferenceCountedByteBuf). To see why io.netty.buffer.UnpooledUnsafeDirectByteBuf got initialized use --trace-class-initialization=io.netty.buffer.UnpooledUnsafeDirectByteBuf
io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf the class was requested to be initialized at run time (subtype of io.netty.buffer.AbstractReferenceCountedByteBuf). To see why io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf got initialized use --trace-class-initialization=io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf
io.netty.buffer.AbstractReferenceCountedByteBuf the class was requested to be initialized at run time (from jar:file:///Users/alexcheng/.m2/repository/io/netty/netty-buffer/4.1.49.Final/netty-buffer-4.1.49.Final.jar!/META-INF/native-image/io.netty/buffer/native-image.properties with 'io.netty.buffer.AbstractReferenceCountedByteBuf'). To see why io.netty.buffer.AbstractReferenceCountedByteBuf got initialized use --trace-class-initialization=io.netty.buffer.AbstractReferenceCountedByteBuf
io.netty.buffer.UnpooledUnsafeNoCleanerDirectByteBuf the class was requested to be initialized at run time (subtype of io.netty.buffer.AbstractReferenceCountedByteBuf). io.netty.buffer.UnpooledUnsafeNoCleanerDirectByteBuf has been initialized without the native-image initialization instrumentation and the stack trace can't be tracked. Try avoiding to initialize the class that caused initialization of io.netty.buffer.UnpooledUnsafeNoCleanerDirectByteBuf
io.netty.internal.tcnative.SSL the class was requested to be initialized at run time (from jar:file:///Users/alexcheng/local/GitHub/happyride/happyride/service/address-service/target/address-service-2.0.0-SNAPSHOT.jar!/META-INF/native-image/org.springframework.aot/spring-aot/native-image.properties with 'io.netty.internal.tcnative'). To see why io.netty.internal.tcnative.SSL got initialized use --trace-class-initialization=io.netty.internal.tcnative.SSL
io.netty.handler.ssl.PemPrivateKey the class was requested to be initialized at run time (subtype of io.netty.util.AbstractReferenceCounted). io.netty.handler.ssl.PemPrivateKey has been initialized without the native-image initialization instrumentation and the stack trace can't be tracked. Try avoiding to initialize the class that caused initialization of io.netty.handler.ssl.PemPrivateKey
io.netty.buffer.PooledByteBufAllocator the class was requested to be initialized at run time (from jar:file:///Users/alexcheng/.m2/repository/io/netty/netty-buffer/4.1.49.Final/netty-buffer-4.1.49.Final.jar!/META-INF/native-image/io.netty/buffer/native-image.properties with 'io.netty.buffer.PooledByteBufAllocator'). io.netty.buffer.PooledByteBufAllocator has been initialized without the native-image initialization instrumentation and the stack trace can't be tracked. Try avoiding to initialize the class that caused initialization of io.netty.buffer.PooledByteBufAllocator
io.netty.handler.ssl.PemValue the class was requested to be initialized at run time (subtype of io.netty.util.AbstractReferenceCounted). To see why io.netty.handler.ssl.PemValue got initialized use --trace-class-initialization=io.netty.handler.ssl.PemValue

As shown in the error message, the --trace-class-initialization option can be used to trace class initialization.

This error is typically caused by the following scenario:

The A class was initialized at image building time. A references another class B. So B should also be initialized at image building time. However, the class B was configured to be initialized at runtime.

For this kind of error, the -H:+PrintClassInitialization option can be used to output the class initialization configurations. In the CSV file, we can search for the class and its package to check its initialization kind. If the initialization kind is BUILD_TIME, we can update its initialization configuration using the --initialize-at-run-time option to force its initialization at runtime.