From ab79745896a79d28f085b97657b5f0a3ae1cea00 Mon Sep 17 00:00:00 2001 From: iklam Date: Fri, 19 Sep 2025 18:32:58 -0700 Subject: [PATCH 1/5] imported --- src/hotspot/share/cds/aotClassInitializer.cpp | 64 ++++++++++++++ src/hotspot/share/cds/aotClassInitializer.hpp | 3 + src/hotspot/share/cds/aotMetaspace.cpp | 29 ++++-- .../share/classfile/classFileParser.cpp | 50 ++--------- .../share/classes/java/lang/Object.java | 2 +- .../classes/jdk/internal/math/MathUtils.java | 2 + .../annotation/AOTSafeClassInitializer.java | 15 ++-- test/hotspot/jtreg/ProblemList-AotJdk.txt | 5 ++ .../aotAnnotations/AOTAnnotationsTest.java | 88 +++++++++++++++++++ 9 files changed, 196 insertions(+), 62 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotAnnotations/AOTAnnotationsTest.java diff --git a/src/hotspot/share/cds/aotClassInitializer.cpp b/src/hotspot/share/cds/aotClassInitializer.cpp index 0c8ea97fba00b..db1548af563f5 100644 --- a/src/hotspot/share/cds/aotClassInitializer.cpp +++ b/src/hotspot/share/cds/aotClassInitializer.cpp @@ -47,7 +47,18 @@ bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) { ik = RegeneratedClasses::get_original_object(ik); } + check_aot_annotations(ik); + + if (ik->force_aot_initialization()) { + assert(ik->is_initialized(), "must have been initialized before this check"); + } + 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; } @@ -214,6 +225,9 @@ bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) { if (ik->has_aot_safe_initializer()) { return true; } + if (ik->force_aot_initialization()) { + return true; + } } #ifdef ASSERT @@ -244,6 +258,56 @@ void AOTClassInitializer::call_runtime_setup(JavaThread* current, InstanceKlass* } } +template +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 + // of a supertype but not the 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 @AOTClassInitializer + 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 diff --git a/src/hotspot/share/cds/aotClassInitializer.hpp b/src/hotspot/share/cds/aotClassInitializer.hpp index 974bbeb903c72..b133e12014612 100644 --- a/src/hotspot/share/cds/aotClassInitializer.hpp +++ b/src/hotspot/share/cds/aotClassInitializer.hpp @@ -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. diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index f866461e7d4c8..ab8d7fe9592c8 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -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 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); - } } // Preload classes from a list, populate the shared spaces and dump to a @@ -1217,6 +1230,8 @@ bool AOTMetaspace::try_link_class(JavaThread* current, InstanceKlass* ik) { return false; } + bool made_progress = false; + if (ik->is_loaded() && !ik->is_linked() && ik->can_be_verified_at_dumptime() && !SystemDictionaryShared::has_class_failed_verification(ik)) { bool saved = BytecodeVerificationLocal; @@ -1242,10 +1257,10 @@ bool AOTMetaspace::try_link_class(JavaThread* current, InstanceKlass* ik) { ik->compute_has_loops_flag_for_methods(); } BytecodeVerificationLocal = saved; - return true; - } else { - return false; + made_progress = true; } + + return made_progress; } void VM_PopulateDumpSharedSpace::dump_java_heap_objects() { diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index 852d23cbc2eea..ee381ccba8dca 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -1900,16 +1900,16 @@ AnnotationCollector::annotation_index(const ClassLoaderData* loader_data, case VM_SYMBOL_ENUM_NAME(java_lang_Deprecated): { return _java_lang_Deprecated; } - case VM_SYMBOL_ENUM_NAME(jdk_internal_vm_annotation_AOTSafeClassInitializer_signature): { - if (_location != _in_class) break; // only allow for classes - if (!privileged) break; // only allow in privileged code - return _jdk_internal_vm_annotation_AOTSafeClassInitializer; - } case VM_SYMBOL_ENUM_NAME(jdk_internal_vm_annotation_AOTRuntimeSetup_signature): { if (_location != _in_method) break; // only allow for methods if (!privileged) break; // only allow in privileged code return _method_AOTRuntimeSetup; } + case VM_SYMBOL_ENUM_NAME(jdk_internal_vm_annotation_AOTSafeClassInitializer_signature): { + if (_location != _in_class) break; // only allow for classes + if (!privileged) break; // only allow in privileged code + return _jdk_internal_vm_annotation_AOTSafeClassInitializer; + } default: { break; } @@ -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 must be included. - // This ensures that in the production run, we don't run the of a supertype but skips - // ik's . - 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 diff --git a/src/java.base/share/classes/java/lang/Object.java b/src/java.base/share/classes/java/lang/Object.java index 11dcab1b005e1..93a2e94f32ac6 100644 --- a/src/java.base/share/classes/java/lang/Object.java +++ b/src/java.base/share/classes/java/lang/Object.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 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 diff --git a/src/java.base/share/classes/jdk/internal/math/MathUtils.java b/src/java.base/share/classes/jdk/internal/math/MathUtils.java index 0de07f8ed605f..172090facc027 100644 --- a/src/java.base/share/classes/jdk/internal/math/MathUtils.java +++ b/src/java.base/share/classes/jdk/internal/math/MathUtils.java @@ -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: diff --git a/src/java.base/share/classes/jdk/internal/vm/annotation/AOTSafeClassInitializer.java b/src/java.base/share/classes/jdk/internal/vm/annotation/AOTSafeClassInitializer.java index 3c2dab3209a7d..9d423159efbb4 100644 --- a/src/java.base/share/classes/jdk/internal/vm/annotation/AOTSafeClassInitializer.java +++ b/src/java.base/share/classes/jdk/internal/vm/annotation/AOTSafeClassInitializer.java @@ -112,21 +112,18 @@ /// 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. /// diff --git a/test/hotspot/jtreg/ProblemList-AotJdk.txt b/test/hotspot/jtreg/ProblemList-AotJdk.txt index 047fc6d33f88c..fbc1c792ec4d1 100644 --- a/test/hotspot/jtreg/ProblemList-AotJdk.txt +++ b/test/hotspot/jtreg/ProblemList-AotJdk.txt @@ -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 diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotAnnotations/AOTAnnotationsTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotAnnotations/AOTAnnotationsTest.java new file mode 100644 index 0000000000000..adfa0dbbefbdc --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotAnnotations/AOTAnnotationsTest.java @@ -0,0 +1,88 @@ +/* + * 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", + }; + } + + @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, it will be cached in aot-inited state. + System.out.println(Double.toString(d)); + } +} From 06c46ef93c3dbbe2b5cbe656391b1e7662b16de7 Mon Sep 17 00:00:00 2001 From: iklam Date: Fri, 19 Sep 2025 19:50:54 -0700 Subject: [PATCH 2/5] Fix --- src/hotspot/share/cds/aotClassInitializer.cpp | 7 ---- src/hotspot/share/cds/aotMetaspace.cpp | 8 ++--- src/hotspot/share/cds/finalImageRecipes.cpp | 34 ++++++++++++------- src/hotspot/share/cds/finalImageRecipes.hpp | 15 ++++---- .../share/classfile/classFileParser.cpp | 10 +++--- .../aotAnnotations/AOTAnnotationsTest.java | 4 ++- 6 files changed, 41 insertions(+), 37 deletions(-) diff --git a/src/hotspot/share/cds/aotClassInitializer.cpp b/src/hotspot/share/cds/aotClassInitializer.cpp index db1548af563f5..b327d8d3c6091 100644 --- a/src/hotspot/share/cds/aotClassInitializer.cpp +++ b/src/hotspot/share/cds/aotClassInitializer.cpp @@ -49,10 +49,6 @@ bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) { check_aot_annotations(ik); - if (ik->force_aot_initialization()) { - assert(ik->is_initialized(), "must have been initialized before this check"); - } - if (!ik->is_initialized() && !ik->is_being_initialized()) { if (ik->has_aot_safe_initializer()) { ResourceMark rm; @@ -225,9 +221,6 @@ bool AOTClassInitializer::can_archive_initialized_mirror(InstanceKlass* ik) { if (ik->has_aot_safe_initializer()) { return true; } - if (ik->force_aot_initialization()) { - return true; - } } #ifdef ASSERT diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index ab8d7fe9592c8..e778df99e0017 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -1230,8 +1230,6 @@ bool AOTMetaspace::try_link_class(JavaThread* current, InstanceKlass* ik) { return false; } - bool made_progress = false; - if (ik->is_loaded() && !ik->is_linked() && ik->can_be_verified_at_dumptime() && !SystemDictionaryShared::has_class_failed_verification(ik)) { bool saved = BytecodeVerificationLocal; @@ -1257,10 +1255,10 @@ bool AOTMetaspace::try_link_class(JavaThread* current, InstanceKlass* ik) { ik->compute_has_loops_flag_for_methods(); } BytecodeVerificationLocal = saved; - made_progress = true; + return true; + } else { + return false; } - - return made_progress; } void VM_PopulateDumpSharedSpace::dump_java_heap_objects() { diff --git a/src/hotspot/share/cds/finalImageRecipes.cpp b/src/hotspot/share/cds/finalImageRecipes.cpp index 2d237edfd2de1..dfe74acd6c1e5 100644 --- a/src/hotspot/share/cds/finalImageRecipes.cpp +++ b/src/hotspot/share/cds/finalImageRecipes.cpp @@ -57,7 +57,7 @@ void FinalImageRecipes::record_recipes_for_constantpool() { // ignored during the final image assembly. GrowableArray*> tmp_cp_recipes; - GrowableArray tmp_cp_flags; + GrowableArray tmp_flags; GrowableArray* 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* 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); + } } } } diff --git a/src/hotspot/share/cds/finalImageRecipes.hpp b/src/hotspot/share/cds/finalImageRecipes.hpp index 3af1e70772c6b..8c6038c2ab4a6 100644 --- a/src/hotspot/share/cds/finalImageRecipes.hpp +++ b/src/hotspot/share/cds/finalImageRecipes.hpp @@ -42,20 +42,21 @@ template 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* _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*>* _cp_recipes; - Array* _cp_flags; + Array* _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(); diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index ee381ccba8dca..a117bcdd9693d 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -1900,16 +1900,16 @@ AnnotationCollector::annotation_index(const ClassLoaderData* loader_data, case VM_SYMBOL_ENUM_NAME(java_lang_Deprecated): { return _java_lang_Deprecated; } - case VM_SYMBOL_ENUM_NAME(jdk_internal_vm_annotation_AOTRuntimeSetup_signature): { - if (_location != _in_method) break; // only allow for methods - if (!privileged) break; // only allow in privileged code - return _method_AOTRuntimeSetup; - } case VM_SYMBOL_ENUM_NAME(jdk_internal_vm_annotation_AOTSafeClassInitializer_signature): { if (_location != _in_class) break; // only allow for classes if (!privileged) break; // only allow in privileged code return _jdk_internal_vm_annotation_AOTSafeClassInitializer; } + case VM_SYMBOL_ENUM_NAME(jdk_internal_vm_annotation_AOTRuntimeSetup_signature): { + if (_location != _in_method) break; // only allow for methods + if (!privileged) break; // only allow in privileged code + return _method_AOTRuntimeSetup; + } default: { break; } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotAnnotations/AOTAnnotationsTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotAnnotations/AOTAnnotationsTest.java index adfa0dbbefbdc..96422702bd911 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotAnnotations/AOTAnnotationsTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotAnnotations/AOTAnnotationsTest.java @@ -60,6 +60,7 @@ public String classpath(RunMode runMode) { public String[] vmArgs(RunMode runMode) { return new String[] { "-Xlog:aot+class=debug", + "-Xlog:aot+init", }; } @@ -82,7 +83,8 @@ public static void main(String args[]) { double d = 12.34567; // Double.toString() uses jdk.internal.math.MathUtils. - // Because MathUtils has @AOTSafeClassInitializer, it will be cached in aot-inited state. + // 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)); } } From 1fa2b1ec3f8ff67513f8bd7233851d2123070217 Mon Sep 17 00:00:00 2001 From: iklam Date: Sat, 20 Sep 2025 17:08:50 -0700 Subject: [PATCH 3/5] updated javadoc for AOTSafeClassInitializer.java --- .../annotation/AOTSafeClassInitializer.java | 49 ++++++++++++------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/vm/annotation/AOTSafeClassInitializer.java b/src/java.base/share/classes/jdk/internal/vm/annotation/AOTSafeClassInitializer.java index 9d423159efbb4..49bf88be90243 100644 --- a/src/java.base/share/classes/jdk/internal/vm/annotation/AOTSafeClassInitializer.java +++ b/src/java.base/share/classes/jdk/internal/vm/annotation/AOTSafeClassInitializer.java @@ -30,25 +30,34 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/// Indicates that the static initializer of this class or interface -/// (its `` method) is allowed to be _AOT-initialized_, -/// because its author considers it safe to execute 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.) -/// -/// During the production run, the static initializer (``) 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. -/// -/// Currently, this annotation is used mainly for supporting AOT -/// linking of APIs, including bootstrap methods, in the -/// `java.lang.invoke` package. +/// 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. +/// +/// For a class or interface _X_ annotated with `@AOTSafeClassInitializer`, it will +/// be initialized in the AOT assembly phase under two circumstances: +/// +/// - If _X_ was initialized during the AOT training run, the JVM will proactively +/// initialize _X_ in the assembly phase. +/// - 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. +/// This is usually the result of performing AOT optimizations for 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 +/// (``) 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 @@ /// along with every relevant superclass and implemented interface, along /// with classes for every object created during the course of static /// initialization (running `` 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 From 85cbcb78c6d6b3dda73576bee8a0d77b4e050264 Mon Sep 17 00:00:00 2001 From: iklam Date: Tue, 23 Sep 2025 15:02:48 -0700 Subject: [PATCH 4/5] @adinn and @liach comments --- src/hotspot/share/cds/aotClassInitializer.cpp | 2 +- src/hotspot/share/cds/aotMetaspace.cpp | 2 +- src/java.base/share/classes/java/lang/Object.java | 2 +- .../vm/annotation/AOTSafeClassInitializer.java | 12 ++++++------ 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/hotspot/share/cds/aotClassInitializer.cpp b/src/hotspot/share/cds/aotClassInitializer.cpp index e66d27a7e3840..00db747622fab 100644 --- a/src/hotspot/share/cds/aotClassInitializer.cpp +++ b/src/hotspot/share/cds/aotClassInitializer.cpp @@ -292,7 +292,7 @@ void AOTClassInitializer::check_aot_annotations(InstanceKlass* ik) { return supertype->has_aot_safe_initializer(); }); } else { - // @AOTRuntimeSetup only meaningful in @AOTClassInitializer + // @AOTRuntimeSetup only meaningful in @AOTSafeClassInitializer if (ik->is_runtime_setup_required()) { ResourceMark rm; log_error(aot, init)("@AOTRuntimeSetup meaningless in non-@AOTSafeClassInitializer class %s", diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index cd44080d390a7..d80383be2720d 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -812,7 +812,7 @@ void AOTMetaspace::link_shared_classes(TRAPS) { // constant pool entries that were resolved during the training run. FinalImageRecipes::apply_recipes(CHECK); - // Because the AOT assembly phase does not run the exact code as in the + // 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 diff --git a/src/java.base/share/classes/java/lang/Object.java b/src/java.base/share/classes/java/lang/Object.java index 93a2e94f32ac6..11dcab1b005e1 100644 --- a/src/java.base/share/classes/java/lang/Object.java +++ b/src/java.base/share/classes/java/lang/Object.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2024, 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 diff --git a/src/java.base/share/classes/jdk/internal/vm/annotation/AOTSafeClassInitializer.java b/src/java.base/share/classes/jdk/internal/vm/annotation/AOTSafeClassInitializer.java index 49bf88be90243..1305a7b6b88bc 100644 --- a/src/java.base/share/classes/jdk/internal/vm/annotation/AOTSafeClassInitializer.java +++ b/src/java.base/share/classes/jdk/internal/vm/annotation/AOTSafeClassInitializer.java @@ -37,12 +37,12 @@ /// For a class or interface _X_ annotated with `@AOTSafeClassInitializer`, it will /// be initialized in the AOT assembly phase under two circumstances: /// -/// - If _X_ was initialized during the AOT training run, the JVM will proactively -/// initialize _X_ in the assembly phase. -/// - 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. -/// This is usually the result of performing AOT optimizations for the -/// `java.lang.invoke` package. +/// 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. /// /// 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, From 774fe0ee85b6d6cf6168cdc3e62b8f3e371fa814 Mon Sep 17 00:00:00 2001 From: iklam Date: Tue, 23 Sep 2025 15:05:15 -0700 Subject: [PATCH 5/5] Exclude new test from hotspot_aot_classlinking and hotspot_appcds_dynamic test groups --- test/hotspot/jtreg/TEST.groups | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index 8169ca87f4e65..580d9bd11d7d6 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -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 \