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.
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.
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
.
Kind | Time | Description |
---|---|---|
BUILD_TIME | Build time | Class is initialized during image building, so it is already initialized at runtime. |
RERUN | Both | Class is initialized both at runtime and during image building. |
RUN_TIME | Run time | Class 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.
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 classB
. SoB
should also be initialized at image building time. However, the classB
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.