◀Back
Specify Class Initialization Explicitly
By default, Native Image initializes application classes at runtime, except for the classes that Native Image proves “safe” for initialization at build time.
However, you can influence the default behavior by specifying the classes to be build-time or run-time initialized explicitly.
For that, there are two command-line options: --initialize-at-build-time
and --initialize-at-run-time
.
You can use these options to specify whole packages or individual classes.
For example, if you have the classes p.C1
, p.C2
, … ,p.Cn
, you can specify that all the classes in the package p
are to be initialized at build time by passing the following option to native-image
:
--initialize-at-build-time=p
If you want only class C1
in package p
to be initialized at runtime, use:
--initialize-at-run-time=p.C1
You can also programmatically specify class initialization using the RuntimeClassInitialization
class from the Native Image Feature interface.
This guide demonstrates how to build a native executable by running the class initializer at runtime (default behavior), and then at build time, and compares the two approaches.
Prerequisite
Make sure you have installed a GraalVM JDK. The easiest way to get started is with SDKMAN!. For other installation options, visit the Downloads section.
Run a Demo
For the demo, run a simple Java application that parses some Java talks from 2023.
The parser creates records and adds them to a List<Talk>
collection.
- Save the following Java source code in a file named TalkParser.java:
import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class TalkParser { private static final List<Talk> TALKS = new ArrayList<>(); static { Scanner s = new Scanner(""" Asynchronous Programming in Java: Options to Choose from by Venkat Subramaniam Anatomy of a Spring Boot App with Clean Architecture by Steve Pember Java in the Cloud with GraalVM by Alina Yurenko Bootiful Spring Boot 3 by Josh Long """); while (s.hasNextLine()) { TALKS.add(new Talk(s.nextLine())); } s.close(); } public static void main(String[] args) { System.out.println("Talks loaded using scanner:"); for (Talk talk : TALKS) { System.out.println("- " + talk.name()); } } } record Talk (String name) {}
- Compile the application:
javac TalkParser.java
- Build a native executable, explicitly running the class initializer at runtime:
native-image --initialize-at-run-time=TalkParser,Talk -o runtime-parser TalkParser
You can leave out the
--initialize-at-run-time=TalkParser,Talk
option in this example because these classes are marked for initialization at run time by default. The-o
option specifies the name of the output file. - Run and
time
the native application:time ./runtime-parser
On a machine with 16 GB of memory and 8 cores, you should see a result similar to: ``` Talks loaded using scanner:
- Asynchronous Programming in Java: Options to Choose from by Venkat Subramaniam
- Anatomy of a Spring Boot App with Clean Architecture by Steve Pember
- Java in the Cloud with GraalVM by Alina Yurenko
- Bootiful Spring Boot 3 by Josh Long ./runtime-parser 0.00s user 0.00s system 52% cpu 0.010 total ``` The application parses the text block at runtime.
Check the file size which should be around 13M:
du -sh runtime-parser
- Next, build a native executable initializing
TalkParser
at build time, and providing a different name for the output file to differentiate it from the previous build. TheTalk
record has to be initialized explicitly too, so the objects of this type will be persisted in the executable heap.native-image --initialize-at-build-time=TalkParser,Talk -o buildtime-parser TalkParser
If your application adds additional types to the executable heap, each type (or the corresponding package) needs to be marked for build-time initialization explicitly to fulfill the requirements of
--strict-image-heap
. An appropriate actionable error message will guide you through the process. - Run and
time
the second executable for comparison:time ./buildtime-parser
This time you should see something similar to this:
Talks loaded using scanner: - Asynchronous Programming in Java: Options to Choose from by Venkat Subramaniam - Anatomy of a Spring Boot App with Clean Architecture by Steve Pember - Java in the Cloud with GraalVM by Alina Yurenko - Bootiful Spring Boot 3 by Josh Long ./buildtime-parser 0.00s user 0.00s system 53% cpu 0.016 total
Check the file size which should decrease to around 6.4M!
du -sh buildtime-parser
The file size change is because Native Image runs the static initializer at build time, parsing the text block, and persisting only the
Talk
records in the executable.As a result, the majority of the scanning infrastructure does not become reachable when Native Image statically analyzes the application and is, therefore, not included in the executable.
Another valuable criterion for profiling applications more accurately is the number of instructions, which can be obtained using the Linux perf
profiler.
For example, for this demo application, the number of instructions decreased by almost 30% (from 11.8M to 8.6M) in the case of build-time class initialization:
perf stat ./runtime-parser
Talks loaded using scanner:
- Asynchronous Programming in Java: Options to Choose from by Venkat Subramaniam
(...)
Performance counter stats for './runtime-parser':
(...)
11,323,415 cycles # 3.252 GHz
11,781,338 instructions # 1.04 insn per cycle
2,264,670 branches # 650.307 M/sec
28,583 branch-misses # 1.26% of all branches
(...)
0.003817438 seconds time elapsed
0.000000000 seconds user
0.003878000 seconds sys
perf stat ./buildtime-parser
Talks loaded using scanner:
- Asynchronous Programming in Java: Options to Choose from by Venkat Subramaniam
(...)
Performance counter stats for './buildtime-parser':
(...)
9,534,318 cycles # 3.870 GHz
8,609,249 instructions # 0.90 insn per cycle
1,640,540 branches # 665.818 M/sec
23,490 branch-misses # 1.43% of all branches
(...)
0.003119519 seconds time elapsed
0.001113000 seconds user
0.002226000 seconds sys
This demonstrates how Native Image can shift work from runtime to build time: when the class is initialized at build time, the text block is parsed when the executable is being built and only the parsed objects are included.
This not only makes the executable smaller in file size, but also faster to run: when the executable runs, the Talk
records already exist and only need to be printed.
To ensure native executables built with Native Image are as compatible as possible with the HotSpot behavior, application classes that cannot be safely initialized at build time, are initialized at runtime.
You as a user, or a framework that you use, must explicitly request build-time initialization for certain classes to benefit from smaller file sizes and faster times to run.
Include the right data structures to avoid the image size blowing up instead.
We also recommend using --initialize-at-build-time
with single classes only.
It may be that you need to add a lot of --initialize-at-build-time
entries.
Note that incorrect build-time initialization can lead to problems that are to be avoided in production settings such as dysfunctional behavior or including sensitive data such as passwords or encryption keys.
Conclusion
This guide demonstrated how you can influence the default native-image
class initialization policy, and configure it to initialize a specific class at build time, depending on the use case.
The benefits of the build-time versus run-time initialization are described in Class Initialization in Native Image, but, in short, build-time initialization can significantly decrease the overall file size and improve the runtime of your application when used correctly.