8368174: Proactive initialization of @AOTSafeClassInitializer classes

Reviewed-by: liach, adinn, asmehra
This commit is contained in:
Ioi Lam 2025-09-24 20:58:26 +00:00
parent 8f87fdce0b
commit 17accf4a06
11 changed files with 239 additions and 88 deletions

View File

@ -48,7 +48,14 @@ bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) {
ik = RegeneratedClasses::get_original_object(ik);
}
check_aot_annotations(ik);
if (!ik->is_initialized() && !ik->is_being_initialized()) {
if (ik->has_aot_safe_initializer()) {
ResourceMark rm;
log_info(aot, init)("Class %s is annotated with @AOTSafeClassInitializer but has not been initialized",
ik->external_name());
}
return false;
}
@ -245,6 +252,56 @@ void AOTClassInitializer::call_runtime_setup(JavaThread* current, InstanceKlass*
}
}
template <typename FUNCTION>
void require_annotation_for_super_types(InstanceKlass* ik, const char* annotation, FUNCTION func) {
if (log_is_enabled(Info, aot, init)) {
ResourceMark rm;
log_info(aot, init)("Found %s class %s", annotation, ik->external_name());
}
// Since ik has this annotation, we require that
// - all super classes must have this annotation
// - all super interfaces that are interface_needs_clinit_execution_as_super()
// must have this annotation
// This avoid the situation where in the production run, we run the <clinit>
// of a supertype but not the <clinit> of ik
InstanceKlass* super = ik->java_super();
if (super != nullptr && !func(super)) {
ResourceMark rm;
log_error(aot, init)("Missing %s in superclass %s for class %s",
annotation, super->external_name(), ik->external_name());
AOTMetaspace::unrecoverable_writing_error();
}
int len = ik->local_interfaces()->length();
for (int i = 0; i < len; i++) {
InstanceKlass* intf = ik->local_interfaces()->at(i);
if (intf->interface_needs_clinit_execution_as_super() && !func(intf)) {
ResourceMark rm;
log_error(aot, init)("Missing %s in superinterface %s for class %s",
annotation, intf->external_name(), ik->external_name());
AOTMetaspace::unrecoverable_writing_error();
}
}
}
void AOTClassInitializer::check_aot_annotations(InstanceKlass* ik) {
if (ik->has_aot_safe_initializer()) {
require_annotation_for_super_types(ik, "@AOTSafeClassInitializer", [&] (const InstanceKlass* supertype) {
return supertype->has_aot_safe_initializer();
});
} else {
// @AOTRuntimeSetup only meaningful in @AOTSafeClassInitializer
if (ik->is_runtime_setup_required()) {
ResourceMark rm;
log_error(aot, init)("@AOTRuntimeSetup meaningless in non-@AOTSafeClassInitializer class %s",
ik->external_name());
}
}
}
#ifdef ASSERT
void AOTClassInitializer::init_test_class(TRAPS) {
// -XX:AOTInitTestClass is used in regression tests for adding additional AOT-initialized classes

View File

@ -31,6 +31,9 @@
class InstanceKlass;
class AOTClassInitializer : AllStatic {
static void check_aot_annotations(InstanceKlass* ik);
public:
// Called by heapShared.cpp to see if src_ik->java_mirror() can be archived in
// the initialized state.

View File

@ -804,6 +804,23 @@ void AOTMetaspace::link_shared_classes(TRAPS) {
AOTClassLinker::initialize();
AOTClassInitializer::init_test_class(CHECK);
if (CDSConfig::is_dumping_final_static_archive()) {
// - Load and link all classes used in the training run.
// - Initialize @AOTSafeClassInitializer classes that were
// initialized in the training run.
// - Perform per-class optimization such as AOT-resolution of
// constant pool entries that were resolved during the training run.
FinalImageRecipes::apply_recipes(CHECK);
// Because the AOT assembly phase does not run the same exact code as in the
// training run (e.g., we use different lambda form invoker classes;
// generated lambda form classes are not recorded in FinalImageRecipes),
// the recipes do not cover all classes that have been loaded so far. As
// a result, we might have some unlinked classes at this point. Since we
// require cached classes to be linked, all such classes will be linked
// by the following step.
}
link_all_loaded_classes(THREAD);
// Eargerly resolve all string constants in constant pools
@ -817,10 +834,6 @@ void AOTMetaspace::link_shared_classes(TRAPS) {
AOTConstantPoolResolver::preresolve_string_cp_entries(ik, CHECK);
}
}
if (CDSConfig::is_dumping_final_static_archive()) {
FinalImageRecipes::apply_recipes(CHECK);
}
}
void AOTMetaspace::dump_static_archive(TRAPS) {

View File

@ -57,7 +57,7 @@ void FinalImageRecipes::record_recipes_for_constantpool() {
// ignored during the final image assembly.
GrowableArray<Array<int>*> tmp_cp_recipes;
GrowableArray<int> tmp_cp_flags;
GrowableArray<int> tmp_flags;
GrowableArray<Klass*>* klasses = ArchiveBuilder::current()->klasses();
for (int i = 0; i < klasses->length(); i++) {
@ -70,12 +70,16 @@ void FinalImageRecipes::record_recipes_for_constantpool() {
ConstantPool* cp = ik->constants();
ConstantPoolCache* cp_cache = cp->cache();
if (ik->is_initialized()) {
flags |= WAS_INITED;
}
for (int cp_index = 1; cp_index < cp->length(); cp_index++) { // Index 0 is unused
if (cp->tag_at(cp_index).value() == JVM_CONSTANT_Class) {
Klass* k = cp->resolved_klass_at(cp_index);
if (k->is_instance_klass()) {
cp_indices.append(cp_index);
flags |= HAS_CLASS;
flags |= CP_RESOLVE_CLASS;
}
}
}
@ -88,7 +92,7 @@ void FinalImageRecipes::record_recipes_for_constantpool() {
if (rfe->is_resolved(Bytecodes::_getfield) ||
rfe->is_resolved(Bytecodes::_putfield)) {
cp_indices.append(rfe->constant_pool_index());
flags |= HAS_FIELD_AND_METHOD;
flags |= CP_RESOLVE_FIELD_AND_METHOD;
}
}
}
@ -103,7 +107,7 @@ void FinalImageRecipes::record_recipes_for_constantpool() {
rme->is_resolved(Bytecodes::_invokestatic) ||
rme->is_resolved(Bytecodes::_invokehandle)) {
cp_indices.append(rme->constant_pool_index());
flags |= HAS_FIELD_AND_METHOD;
flags |= CP_RESOLVE_FIELD_AND_METHOD;
}
}
}
@ -115,7 +119,7 @@ void FinalImageRecipes::record_recipes_for_constantpool() {
int cp_index = rie->constant_pool_index();
if (rie->is_resolved()) {
cp_indices.append(cp_index);
flags |= HAS_INDY;
flags |= CP_RESOLVE_INDY;
}
}
}
@ -127,14 +131,14 @@ void FinalImageRecipes::record_recipes_for_constantpool() {
} else {
tmp_cp_recipes.append(nullptr);
}
tmp_cp_flags.append(flags);
tmp_flags.append(flags);
}
_cp_recipes = ArchiveUtils::archive_array(&tmp_cp_recipes);
ArchivePtrMarker::mark_pointer(&_cp_recipes);
_cp_flags = ArchiveUtils::archive_array(&tmp_cp_flags);
ArchivePtrMarker::mark_pointer(&_cp_flags);
_flags = ArchiveUtils::archive_array(&tmp_flags);
ArchivePtrMarker::mark_pointer(&_flags);
}
void FinalImageRecipes::apply_recipes_for_constantpool(JavaThread* current) {
@ -142,7 +146,7 @@ void FinalImageRecipes::apply_recipes_for_constantpool(JavaThread* current) {
for (int i = 0; i < _all_klasses->length(); i++) {
Array<int>* cp_indices = _cp_recipes->at(i);
int flags = _cp_flags->at(i);
int flags = _flags->at(i);
if (cp_indices != nullptr) {
InstanceKlass* ik = InstanceKlass::cast(_all_klasses->at(i));
if (ik->is_loaded()) {
@ -152,13 +156,13 @@ void FinalImageRecipes::apply_recipes_for_constantpool(JavaThread* current) {
for (int j = 0; j < cp_indices->length(); j++) {
preresolve_list.at_put(cp_indices->at(j), true);
}
if ((flags & HAS_CLASS) != 0) {
if ((flags & CP_RESOLVE_CLASS) != 0) {
AOTConstantPoolResolver::preresolve_class_cp_entries(current, ik, &preresolve_list);
}
if ((flags & HAS_FIELD_AND_METHOD) != 0) {
if ((flags & CP_RESOLVE_FIELD_AND_METHOD) != 0) {
AOTConstantPoolResolver::preresolve_field_and_method_cp_entries(current, ik, &preresolve_list);
}
if ((flags & HAS_INDY) != 0) {
if ((flags & CP_RESOLVE_INDY) != 0) {
AOTConstantPoolResolver::preresolve_indy_cp_entries(current, ik, &preresolve_list);
}
}
@ -171,6 +175,7 @@ void FinalImageRecipes::load_all_classes(TRAPS) {
Handle class_loader(THREAD, SystemDictionary::java_system_loader());
for (int i = 0; i < _all_klasses->length(); i++) {
Klass* k = _all_klasses->at(i);
int flags = _flags->at(i);
if (k->is_instance_klass()) {
InstanceKlass* ik = InstanceKlass::cast(k);
if (ik->defined_by_other_loaders()) {
@ -188,6 +193,11 @@ void FinalImageRecipes::load_all_classes(TRAPS) {
}
assert(ik->is_loaded(), "must be");
ik->link_class(CHECK);
if (ik->has_aot_safe_initializer() && (flags & WAS_INITED) != 0) {
assert(ik->class_loader() == nullptr, "supported only for boot classes for now");
ik->initialize(CHECK);
}
}
}
}

View File

@ -42,20 +42,21 @@ template <typename T> class Array;
// - The list of all classes that are stored in the AOTConfiguration file.
// - The list of all classes that require AOT resolution of invokedynamic call sites.
class FinalImageRecipes {
static constexpr int HAS_CLASS = 0x1;
static constexpr int HAS_FIELD_AND_METHOD = 0x2;
static constexpr int HAS_INDY = 0x4;
static constexpr int CP_RESOLVE_CLASS = 0x1 << 0; // CP has preresolved class entries
static constexpr int CP_RESOLVE_FIELD_AND_METHOD = 0x1 << 1; // CP has preresolved field/method entries
static constexpr int CP_RESOLVE_INDY = 0x1 << 2; // CP has preresolved indy entries
static constexpr int WAS_INITED = 0x1 << 3; // Class was initialized during training run
// A list of all the archived classes from the preimage. We want to transfer all of these
// into the final image.
Array<Klass*>* _all_klasses;
// For each klass k _all_klasses->at(i), _cp_recipes->at(i) lists all the {klass,field,method,indy}
// cp indices that were resolved for k during the training run.
// For each klass k _all_klasses->at(i): _cp_recipes->at(i) lists all the {klass,field,method,indy}
// cp indices that were resolved for k during the training run; _flags->at(i) has extra info about k.
Array<Array<int>*>* _cp_recipes;
Array<int>* _cp_flags;
Array<int>* _flags;
FinalImageRecipes() : _all_klasses(nullptr), _cp_recipes(nullptr), _cp_flags(nullptr) {}
FinalImageRecipes() : _all_klasses(nullptr), _cp_recipes(nullptr), _flags(nullptr) {}
void* operator new(size_t size) throw();

View File

@ -5163,46 +5163,6 @@ void ClassFileParser::fill_instance_klass(InstanceKlass* ik,
if (_parsed_annotations->has_any_annotations())
_parsed_annotations->apply_to(ik);
// AOT-related checks.
// Note we cannot check this in general due to instrumentation or module patching
if (CDSConfig::is_initing_classes_at_dump_time()) {
// Check the aot initialization safe status.
// @AOTSafeClassInitializer is used only to support ahead-of-time initialization of classes
// in the AOT assembly phase.
if (ik->has_aot_safe_initializer()) {
// If a type is included in the tables inside can_archive_initialized_mirror(), we require that
// - all super classes must be included
// - all super interfaces that have <clinit> must be included.
// This ensures that in the production run, we don't run the <clinit> of a supertype but skips
// ik's <clinit>.
if (_super_klass != nullptr) {
guarantee_property(_super_klass->has_aot_safe_initializer(),
"Missing @AOTSafeClassInitializer in superclass %s for class %s",
_super_klass->external_name(),
CHECK);
}
int len = _local_interfaces->length();
for (int i = 0; i < len; i++) {
InstanceKlass* intf = _local_interfaces->at(i);
guarantee_property(intf->class_initializer() == nullptr || intf->has_aot_safe_initializer(),
"Missing @AOTSafeClassInitializer in superinterface %s for class %s",
intf->external_name(),
CHECK);
}
if (log_is_enabled(Info, aot, init)) {
ResourceMark rm;
log_info(aot, init)("Found @AOTSafeClassInitializer class %s", ik->external_name());
}
} else {
// @AOTRuntimeSetup only meaningful in @AOTClassInitializer
guarantee_property(!ik->is_runtime_setup_required(),
"@AOTRuntimeSetup meaningless in non-@AOTSafeClassInitializer class %s",
CHECK);
}
}
apply_parsed_class_attributes(ik);
// Miranda methods

View File

@ -26,12 +26,14 @@
package jdk.internal.math;
import jdk.internal.vm.annotation.Stable;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
/**
* This class exposes package private utilities for other classes.
* Thus, all methods are assumed to be invoked with correct arguments,
* so these are not checked at all.
*/
@AOTSafeClassInitializer
final class MathUtils {
/*
* For full details about this code see the following reference:

View File

@ -30,25 +30,34 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/// Indicates that the static initializer of this class or interface
/// (its `<clinit>` method) is allowed to be _AOT-initialized_,
/// because its author considers it safe to execute during the AOT
/// assembly phase.
/// Indicates that the annotated class or interface is allowed to be _AOT-initialized_,
/// because its author considers it safe to execute the static initializer of
/// the class or interface during the AOT assembly phase.
///
/// This annotation directs the VM to expect that normal execution of Java code
/// during the assembly phase could trigger initialization of this class,
/// and if that happens, to store the resulting static field values in the
/// AOT cache. (These fields happen to be allocated in the `Class` mirror.)
/// For a class or interface _X_ annotated with `@AOTSafeClassInitializer`, it will
/// be initialized in the AOT assembly phase under two circumstances:
///
/// During the production run, the static initializer (`<clinit>`) of
/// this class or interface will not be executed, if it was already
/// executed during the assembling of the AOT being used to start the
/// production run. In that case the resulting static field states
/// (within the `Class` mirror) were already stored in the AOT cache.
/// 1. If _X_ was initialized during the AOT training run, the JVM will proactively
/// initialize _X_ in the assembly phase.
/// 2. If _X_ was not initialized during the AOT training run, the initialization of
/// _X_ can still be triggered by normal execution of Java code in the assembly
/// phase. At present this is usually the result of performing AOT optimizations for
/// the `java.lang.invoke` package but it may include other cases as well.
///
/// Currently, this annotation is used mainly for supporting AOT
/// linking of APIs, including bootstrap methods, in the
/// `java.lang.invoke` package.
/// If _X_ is initialized during the AOT assembly phase, the VM will store
/// the values of the static fields of _X_ in the AOT cache. Consequently,
/// during the production run that uses this AOT cache, the static initializer
/// (`<clinit>`) of _X_ will not be executed. _X_ will appear to be in the
/// "initialized" state and all the cached values of the static field of _X_
/// will be available immediately upon the start of the prodcution run.
///
/// Currently, this annotation is used mainly for two purposes:
///
/// - To AOT-initialize complex static fields whose values are always the same
/// across JVM lifetimes. One example is the tables of constant values
/// in the `jdk.internal.math.MathUtils` class.
/// - To support AOT linking of APIs, including bootstrap methods, in the
/// `java.lang.invoke` package.
///
/// In more detail, the AOT assembly phase performs the following:
///
@ -62,6 +71,8 @@ import java.lang.annotation.Target;
/// along with every relevant superclass and implemented interface, along
/// with classes for every object created during the course of static
/// initialization (running `<clinit>` for each such class or interface).
/// 5. In addition, any class/interface annotated with `@AOTSafeClassInitializer`
/// that was initialized during the training run is proactively initialized.
///
/// Thus, in order to determine that a class or interface _X_ is safe to
/// AOT-initialize requires evaluating every other class or interface _Y_ that
@ -112,21 +123,18 @@ import java.lang.annotation.Target;
/// remotely) if the execution of such an API touches _X_ for initialization,
/// or even if such an API request is in any way sensitive to values stored in
/// the fields of _X_, even if the sensitivity is a simple reference identity
/// test. As noted above, all supertypes of _X_ must also have the
/// `@AOTSafeClassInitializer` annotation, and must also be safe for AOT
/// initialization.
/// test.
///
/// The author of an AOT-initialized class may elect to patch some states at
/// production startup, using an [AOTRuntimeSetup] method, as long as the
/// pre-patched field values (present during AOT assembly) are determined to be
/// compatible with the post-patched values that apply to the production run.
///
/// In the assembly phase, `classFileParser.cpp` performs checks on the annotated
/// classes, to ensure all supertypes of this class that must be initialized when
/// this class is initialized have the `@AOTSafeClassInitializer` annotation.
/// Otherwise, a [ClassFormatError] will be thrown. (This assembly phase restriction
/// allows module patching and instrumentation to work on annotated classes when
/// AOT is not enabled)
/// Before adding this annotation to a class _X_, the author must determine
/// that it's safe to execute the static initializer of _X_ during the AOT
/// assembly phase. In addition, all supertypes of _X_ must also have this
/// annotation. If a supertype of _X_ is found to be missing this annotation,
/// the AOT assembly phase will fail.
///
/// This annotation is only recognized on privileged code and is ignored elsewhere.
///

View File

@ -2,7 +2,12 @@ runtime/modules/PatchModule/PatchModuleClassList.java 0000000 generic-all
runtime/NMT/NMTWithCDS.java 0000000 generic-all
runtime/symbols/TestSharedArchiveConfigFile.java 0000000 generic-all
# The following tests use very small -Xmx and will not be able to
# use the AOT cache generated by "make test JTREG=AOT_JDK=onestep ..."
gc/arguments/TestG1HeapSizeFlags.java 0000000 generic-all
gc/arguments/TestParallelHeapSizeFlags.java 0000000 generic-all
gc/arguments/TestSerialHeapSizeFlags.java 0000000 generic-all
gc/arguments/TestCompressedClassFlags.java 0000000 generic-all
gc/TestAllocateHeapAtMultiple.java 0000000 generic-all
gc/TestAllocateHeapAt.java 0000000 generic-all

View File

@ -412,6 +412,7 @@ hotspot_cds_only = \
hotspot_appcds_dynamic = \
runtime/cds/appcds/ \
-runtime/cds/appcds/aotAnnotations \
-runtime/cds/appcds/aotCache \
-runtime/cds/appcds/aotClassLinking \
-runtime/cds/appcds/aotCode \
@ -512,6 +513,7 @@ hotspot_cds_epsilongc = \
# test AOT class linking, so there's no need to run them again with -XX:+AOTClassLinking.
hotspot_aot_classlinking = \
runtime/cds \
-runtime/cds/appcds/aotAnnotations \
-runtime/cds/appcds/aotCache \
-runtime/cds/appcds/aotClassLinking \
-runtime/cds/appcds/aotCode \

View File

@ -0,0 +1,90 @@
/*
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
/*
* @test
* @summary Tests the effect of jdk.internal.vm.annotation.AOTXXX annotations
* in the core Java library.
* @bug 8317269
* @requires vm.cds.supports.aot.class.linking
* @library /test/jdk/lib/testlibrary /test/lib
* @build AOTAnnotationsTest
* @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar AOTAnnotationsTestApp
* @run driver AOTAnnotationsTest
*/
import jdk.test.lib.cds.CDSAppTester;
import jdk.test.lib.helpers.ClassFileInstaller;
import jdk.test.lib.process.OutputAnalyzer;
public class AOTAnnotationsTest {
static final String appJar = ClassFileInstaller.getJarPath("app.jar");
static final String mainClass = AOTAnnotationsTestApp.class.getName();
public static void main(String[] args) throws Exception {
Tester tester = new Tester();
tester.run(new String[] {"AOT", "--two-step-training"} );
}
static class Tester extends CDSAppTester {
public Tester() {
super(mainClass);
}
@Override
public String classpath(RunMode runMode) {
return appJar;
}
@Override
public String[] vmArgs(RunMode runMode) {
return new String[] {
"-Xlog:aot+class=debug",
"-Xlog:aot+init",
};
}
@Override
public String[] appCommandLine(RunMode runMode) {
return new String[] { mainClass};
}
@Override
public void checkExecution(OutputAnalyzer out, RunMode runMode) {
if (runMode == RunMode.ASSEMBLY) {
out.shouldMatch("jdk.internal.math.MathUtils .*inited");
}
}
}
}
class AOTAnnotationsTestApp {
public static void main(String args[]) {
double d = 12.34567;
// Double.toString() uses jdk.internal.math.MathUtils.
// Because MathUtils has @AOTSafeClassInitializer and was initialized during
// the training run, it will be cached in aot-inited state.
System.out.println(Double.toString(d));
}
}