diff --git a/src/hotspot/share/cds/aotClassInitializer.cpp b/src/hotspot/share/cds/aotClassInitializer.cpp index 0c8ea97fba0..4a98ccbf990 100644 --- a/src/hotspot/share/cds/aotClassInitializer.cpp +++ b/src/hotspot/share/cds/aotClassInitializer.cpp @@ -23,6 +23,7 @@ */ #include "cds/aotClassInitializer.hpp" +#include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/archiveBuilder.hpp" #include "cds/cdsConfig.hpp" #include "cds/heapShared.hpp" diff --git a/src/hotspot/share/cds/aotClassLinker.cpp b/src/hotspot/share/cds/aotClassLinker.cpp index 0eb8f141c20..c66435b03bb 100644 --- a/src/hotspot/share/cds/aotClassLinker.cpp +++ b/src/hotspot/share/cds/aotClassLinker.cpp @@ -192,7 +192,7 @@ void AOTClassLinker::write_to_archive() { if (CDSConfig::is_dumping_aot_linked_classes()) { AOTLinkedClassTable* table = AOTLinkedClassTable::get(); - table->set_boot(write_classes(nullptr, true)); + table->set_boot1(write_classes(nullptr, true)); table->set_boot2(write_classes(nullptr, false)); table->set_platform(write_classes(SystemDictionary::java_platform_loader(), false)); table->set_app(write_classes(SystemDictionary::java_system_loader(), false)); diff --git a/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp index 8795a29fd5c..e7145b25457 100644 --- a/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp +++ b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp @@ -29,6 +29,8 @@ #include "cds/cdsConfig.hpp" #include "cds/heapShared.hpp" #include "classfile/classLoaderData.hpp" +#include "classfile/classLoaderDataShared.hpp" +#include "classfile/javaClasses.hpp" #include "classfile/systemDictionary.hpp" #include "classfile/systemDictionaryShared.hpp" #include "classfile/vmClasses.hpp" @@ -41,66 +43,197 @@ #include "runtime/handles.inline.hpp" #include "runtime/java.hpp" -bool AOTLinkedClassBulkLoader::_boot2_completed = false; -bool AOTLinkedClassBulkLoader::_platform_completed = false; -bool AOTLinkedClassBulkLoader::_app_completed = false; -bool AOTLinkedClassBulkLoader::_all_completed = false; - void AOTLinkedClassBulkLoader::serialize(SerializeClosure* soc) { AOTLinkedClassTable::get()->serialize(soc); } -bool AOTLinkedClassBulkLoader::class_preloading_finished() { - if (!CDSConfig::is_using_aot_linked_classes()) { - return true; - } else { - // The ConstantPools of preloaded classes have references to other preloaded classes. We don't - // want any Java code (including JVMCI compiler) to use these classes until all of them - // are loaded. - return AtomicAccess::load_acquire(&_all_completed); +// This function is called before the VM executes any Java code (include AOT-compiled Java methods). +// +// We populate the boot/platform/app class loaders with classes from the AOT cache. This is a fundamental +// step in restoring the JVM's state from the snapshot recorded in the AOT cache: other AOT optimizations +// such as AOT compiled methods can make direct references to the preloaded classes, knowing that +// these classes are guaranteed to be in at least the "loaded" state. +void AOTLinkedClassBulkLoader::preload_classes(JavaThread* current) { + preload_classes_impl(current); + if (current->has_pending_exception()) { + exit_on_exception(current); } } -void AOTLinkedClassBulkLoader::load_javabase_classes(JavaThread* current) { - assert(CDSConfig::is_using_aot_linked_classes(), "sanity"); - load_classes_in_loader(current, AOTLinkedClassCategory::BOOT1, nullptr); // only java.base classes +void AOTLinkedClassBulkLoader::preload_classes_impl(TRAPS) { + precond(CDSConfig::is_using_aot_linked_classes()); + + ClassLoaderDataShared::restore_archived_modules_for_preloading_classes(THREAD); + Handle h_platform_loader(THREAD, SystemDictionary::java_platform_loader()); + Handle h_system_loader(THREAD, SystemDictionary::java_system_loader()); + + AOTLinkedClassTable* table = AOTLinkedClassTable::get(); + + preload_classes_in_table(table->boot1(), "boot1", Handle(), CHECK); + preload_classes_in_table(table->boot2(), "boot2", Handle(), CHECK); + + initiate_loading(THREAD, "plat", h_platform_loader, table->boot1()); + initiate_loading(THREAD, "plat", h_platform_loader, table->boot2()); + preload_classes_in_table(table->platform(), "plat", h_platform_loader, CHECK); + + initiate_loading(THREAD, "app", h_system_loader, table->boot1()); + initiate_loading(THREAD, "app", h_system_loader, table->boot2()); + initiate_loading(THREAD, "app", h_system_loader, table->platform()); + preload_classes_in_table(table->app(), "app", h_system_loader, CHECK); } -void AOTLinkedClassBulkLoader::load_non_javabase_classes(JavaThread* current) { +void AOTLinkedClassBulkLoader::preload_classes_in_table(Array* classes, + const char* category_name, Handle loader, TRAPS) { + if (classes == nullptr) { + return; + } + + for (int i = 0; i < classes->length(); i++) { + InstanceKlass* ik = classes->at(i); + if (log_is_enabled(Info, aot, load)) { + ResourceMark rm(THREAD); + log_info(aot, load)("%-5s %s%s", category_name, ik->external_name(), + ik->is_hidden() ? " (hidden)" : ""); + } + + SystemDictionary::preload_class(loader, ik, CHECK); + + if (ik->is_hidden()) { + DEBUG_ONLY({ + // Make sure we don't make this hidden class available by name, even if we don't + // use any special ClassLoaderData. + ResourceMark rm(THREAD); + assert(SystemDictionary::find_instance_klass(THREAD, ik->name(), loader) == nullptr, + "hidden classes cannot be accessible by name: %s", ik->external_name()); + }); + } else { + precond(SystemDictionary::find_instance_klass(THREAD, ik->name(), loader) == ik); + } + } +} + +#ifdef ASSERT +void AOTLinkedClassBulkLoader::validate_module_of_preloaded_classes() { + oop javabase_module_oop = ModuleEntryTable::javabase_moduleEntry()->module_oop(); + for (int i = T_BOOLEAN; i < T_LONG+1; i++) { + TypeArrayKlass* tak = Universe::typeArrayKlass((BasicType)i); + validate_module(tak, "boot1", javabase_module_oop); + } + + JavaThread* current = JavaThread::current(); + Handle h_platform_loader(current, SystemDictionary::java_platform_loader()); + Handle h_system_loader(current, SystemDictionary::java_system_loader()); + AOTLinkedClassTable* table = AOTLinkedClassTable::get(); + + validate_module_of_preloaded_classes_in_table(table->boot1(), "boot1", Handle()); + validate_module_of_preloaded_classes_in_table(table->boot2(), "boot2", Handle()); + validate_module_of_preloaded_classes_in_table(table->platform(), "plat", h_platform_loader); + validate_module_of_preloaded_classes_in_table(table->app(), "app", h_system_loader); +} + +void AOTLinkedClassBulkLoader::validate_module_of_preloaded_classes_in_table(Array* classes, + const char* category_name, Handle loader) { + if (classes == nullptr) { + return; + } + + ClassLoaderData* loader_data = ClassLoaderData::class_loader_data(loader()); + for (int i = 0; i < classes->length(); i++) { + InstanceKlass* ik = classes->at(i); + PackageEntry* pkg_entry = ik->package(); + oop module_oop; + if (pkg_entry == nullptr) { + module_oop = loader_data->unnamed_module()->module_oop(); + } else { + module_oop = pkg_entry->module()->module_oop(); + } + + validate_module(ik, category_name, module_oop); + } +} + +void AOTLinkedClassBulkLoader::validate_module(Klass* k, const char* category_name, oop module_oop) { + assert(module_oop != nullptr, "module system must have been initialized"); + + if (log_is_enabled(Debug, aot, module)) { + ResourceMark rm; + log_debug(aot, module)("Validate module of %-5s %s", category_name, k->external_name()); + } + precond(java_lang_Class::module(k->java_mirror()) == module_oop); + + ArrayKlass* ak = k->array_klass_or_null(); + while (ak != nullptr) { + if (log_is_enabled(Debug, aot, module)) { + ResourceMark rm; + log_debug(aot, module)("Validate module of %-5s %s", category_name, ak->external_name()); + } + precond(java_lang_Class::module(ak->java_mirror()) == module_oop); + ak = ak->array_klass_or_null(); + } +} +#endif + +// Link all java.base classes in the AOTLinkedClassTable. Of those classes, +// move the ones that have been AOT-initialized to the "initialized" state. +void AOTLinkedClassBulkLoader::link_or_init_javabase_classes(JavaThread* current) { + link_or_init_classes_for_loader(Handle(), AOTLinkedClassTable::get()->boot1(), current); + if (current->has_pending_exception()) { + exit_on_exception(current); + } +} + +// Do the same thing as link_or_init_javabase_classes(), but for the classes that are not +// in the java.base module. +void AOTLinkedClassBulkLoader::link_or_init_non_javabase_classes(JavaThread* current) { + link_or_init_non_javabase_classes_impl(current); + if (current->has_pending_exception()) { + exit_on_exception(current); + } +} + +void AOTLinkedClassBulkLoader::link_or_init_non_javabase_classes_impl(TRAPS) { assert(CDSConfig::is_using_aot_linked_classes(), "sanity"); + DEBUG_ONLY(validate_module_of_preloaded_classes()); + // is_using_aot_linked_classes() requires is_using_full_module_graph(). As a result, // the platform/system class loader should already have been initialized as part // of the FMG support. assert(CDSConfig::is_using_full_module_graph(), "must be"); - assert(SystemDictionary::java_platform_loader() != nullptr, "must be"); - assert(SystemDictionary::java_system_loader() != nullptr, "must be"); - load_classes_in_loader(current, AOTLinkedClassCategory::BOOT2, nullptr); // all boot classes outside of java.base - _boot2_completed = true; + Handle h_platform_loader(THREAD, SystemDictionary::java_platform_loader()); + Handle h_system_loader(THREAD, SystemDictionary::java_system_loader()); - load_classes_in_loader(current, AOTLinkedClassCategory::PLATFORM, SystemDictionary::java_platform_loader()); - _platform_completed = true; + assert(h_platform_loader() != nullptr, "must be"); + assert(h_system_loader() != nullptr, "must be"); - load_classes_in_loader(current, AOTLinkedClassCategory::APP, SystemDictionary::java_system_loader()); + AOTLinkedClassTable* table = AOTLinkedClassTable::get(); + link_or_init_classes_for_loader(Handle(), table->boot2(), CHECK); + link_or_init_classes_for_loader(h_platform_loader, table->platform(), CHECK); + link_or_init_classes_for_loader(h_system_loader, table->app(), CHECK); + + if (Universe::is_fully_initialized() && VerifyDuringStartup) { + // Make sure we're still in a clean state. + VM_Verify verify_op; + VMThread::execute(&verify_op); + } if (AOTPrintTrainingInfo) { tty->print_cr("==================== archived_training_data ** after all classes preloaded ===================="); TrainingData::print_archived_training_data_on(tty); } - - _app_completed = true; - AtomicAccess::release_store(&_all_completed, true); } -void AOTLinkedClassBulkLoader::load_classes_in_loader(JavaThread* current, AOTLinkedClassCategory class_category, oop class_loader_oop) { - load_classes_in_loader_impl(class_category, class_loader_oop, current); - if (current->has_pending_exception()) { - // We cannot continue, as we might have loaded some of the aot-linked classes, which - // may have dangling C++ pointers to other aot-linked classes that we have failed to load. - exit_on_exception(current); - } -} +// For the AOT cache to function properly, all classes in the AOTLinkedClassTable +// must be loaded and linked. In addition, AOT-initialized classes must be moved to +// the initialized state. +// +// We can encounter a failure during the loading, linking, or initialization of +// classes in the AOTLinkedClassTable only if: +// - We ran out of memory, +// - There is a serious error in the VM implemenation +// When this happens, the VM may be in an inconsistent state (e.g., we have a cached +// heap object of class X, but X is not linked). We must exit the JVM now. void AOTLinkedClassBulkLoader::exit_on_exception(JavaThread* current) { assert(current->has_pending_exception(), "precondition"); @@ -115,117 +248,6 @@ void AOTLinkedClassBulkLoader::exit_on_exception(JavaThread* current) { vm_exit_during_initialization("Unexpected exception when loading aot-linked classes."); } -void AOTLinkedClassBulkLoader::load_classes_in_loader_impl(AOTLinkedClassCategory class_category, oop class_loader_oop, TRAPS) { - Handle h_loader(THREAD, class_loader_oop); - AOTLinkedClassTable* table = AOTLinkedClassTable::get(); - load_table(table, class_category, h_loader, CHECK); - - // Initialize the InstanceKlasses of all archived heap objects that are reachable from the - // archived java class mirrors. - switch (class_category) { - case AOTLinkedClassCategory::BOOT1: - // Delayed until finish_loading_javabase_classes(), as the VM is not ready to - // execute some of the methods. - break; - case AOTLinkedClassCategory::BOOT2: - init_required_classes_for_loader(h_loader, table->boot2(), CHECK); - break; - case AOTLinkedClassCategory::PLATFORM: - init_required_classes_for_loader(h_loader, table->platform(), CHECK); - break; - case AOTLinkedClassCategory::APP: - init_required_classes_for_loader(h_loader, table->app(), CHECK); - break; - case AOTLinkedClassCategory::UNREGISTERED: - ShouldNotReachHere(); - break; - } - - if (Universe::is_fully_initialized() && VerifyDuringStartup) { - // Make sure we're still in a clean state. - VM_Verify verify_op; - VMThread::execute(&verify_op); - } -} - -void AOTLinkedClassBulkLoader::load_table(AOTLinkedClassTable* table, AOTLinkedClassCategory class_category, Handle loader, TRAPS) { - if (class_category != AOTLinkedClassCategory::BOOT1) { - assert(Universe::is_module_initialized(), "sanity"); - } - - const char* category_name = AOTClassLinker::class_category_name(class_category); - switch (class_category) { - case AOTLinkedClassCategory::BOOT1: - load_classes_impl(class_category, table->boot(), category_name, loader, CHECK); - break; - - case AOTLinkedClassCategory::BOOT2: - load_classes_impl(class_category, table->boot2(), category_name, loader, CHECK); - break; - - case AOTLinkedClassCategory::PLATFORM: - { - initiate_loading(THREAD, category_name, loader, table->boot()); - initiate_loading(THREAD, category_name, loader, table->boot2()); - load_classes_impl(class_category, table->platform(), category_name, loader, CHECK); - } - break; - case AOTLinkedClassCategory::APP: - { - initiate_loading(THREAD, category_name, loader, table->boot()); - initiate_loading(THREAD, category_name, loader, table->boot2()); - initiate_loading(THREAD, category_name, loader, table->platform()); - load_classes_impl(class_category, table->app(), category_name, loader, CHECK); - } - break; - case AOTLinkedClassCategory::UNREGISTERED: - default: - ShouldNotReachHere(); // Currently aot-linked classes are not supported for this category. - break; - } -} - -void AOTLinkedClassBulkLoader::load_classes_impl(AOTLinkedClassCategory class_category, Array* classes, - const char* category_name, Handle loader, TRAPS) { - if (classes == nullptr) { - return; - } - - ClassLoaderData* loader_data = ClassLoaderData::class_loader_data(loader()); - - for (int i = 0; i < classes->length(); i++) { - InstanceKlass* ik = classes->at(i); - if (log_is_enabled(Info, aot, load)) { - ResourceMark rm(THREAD); - log_info(aot, load)("%-5s %s%s%s", category_name, ik->external_name(), - ik->is_loaded() ? " (already loaded)" : "", - ik->is_hidden() ? " (hidden)" : ""); - } - - if (!ik->is_loaded()) { - if (ik->is_hidden()) { - load_hidden_class(loader_data, ik, CHECK); - } else { - InstanceKlass* actual; - if (loader_data == ClassLoaderData::the_null_class_loader_data()) { - actual = SystemDictionary::load_instance_class(ik->name(), loader, CHECK); - } else { - actual = SystemDictionaryShared::find_or_load_shared_class(ik->name(), loader, CHECK); - } - - if (actual != ik) { - ResourceMark rm(THREAD); - log_error(aot)("Unable to resolve %s class from %s: %s", category_name, CDSConfig::type_of_archive_being_loaded(), ik->external_name()); - log_error(aot)("Expected: " INTPTR_FORMAT ", actual: " INTPTR_FORMAT, p2i(ik), p2i(actual)); - log_error(aot)("JVMTI class retransformation is not supported when archive was generated with -XX:+AOTClassLinking."); - AOTMetaspace::unrecoverable_loading_error(); - } - assert(actual->is_loaded(), "must be"); - } - } - } -} - // Initiate loading of the in the . The should have already been loaded // by a parent loader of the . This is necessary for handling pre-resolved CP entries. // @@ -255,7 +277,7 @@ void AOTLinkedClassBulkLoader::initiate_loading(JavaThread* current, const char* if (log_is_enabled(Info, aot, load)) { ResourceMark rm(current); const char* defining_loader = (ik->class_loader() == nullptr ? "boot" : "plat"); - log_info(aot, load)("%s %s (initiated, defined by %s)", category_name, ik->external_name(), + log_info(aot, load)("%-5s %s (initiated, defined by %s)", category_name, ik->external_name(), defining_loader); } SystemDictionary::add_to_initiating_loader(current, ik, loader_data); @@ -263,81 +285,11 @@ void AOTLinkedClassBulkLoader::initiate_loading(JavaThread* current, const char* } } -// Currently, we archive only three types of hidden classes: -// - LambdaForms -// - lambda proxy classes -// - StringConcat classes -// See HeapShared::is_archivable_hidden_klass(). -// -// LambdaForm classes (with names like java/lang/invoke/LambdaForm$MH+0x800000015) logically -// belong to the boot loader, but they are usually stored in their own special ClassLoaderData to -// facilitate class unloading, as a LambdaForm may refer to a class loaded by a custom loader -// that may be unloaded. -// -// We only support AOT-resolution of indys in the boot/platform/app loader, so there's no need -// to support class unloading. For simplicity, we put all archived LambdaForm classes in the -// "main" ClassLoaderData of the boot loader. -// -// (Even if we were to support other loaders, we would still feel free to ignore any requirement -// of class unloading, for any class asset in the AOT cache. Anything that makes it into the AOT -// cache has a lifetime dispensation from unloading. After all, the AOT cache never grows, and -// we can assume that the user is content with its size, and doesn't need its footprint to shrink.) -// -// Lambda proxy classes are normally stored in the same ClassLoaderData as their nest hosts, and -// StringConcat are normally stored in the main ClassLoaderData of the boot class loader. We -// do the same for the archived copies of such classes. -void AOTLinkedClassBulkLoader::load_hidden_class(ClassLoaderData* loader_data, InstanceKlass* ik, TRAPS) { - assert(HeapShared::is_lambda_form_klass(ik) || - HeapShared::is_lambda_proxy_klass(ik) || - HeapShared::is_string_concat_klass(ik), "sanity"); - DEBUG_ONLY({ - assert(ik->super()->is_loaded(), "must be"); - for (int i = 0; i < ik->local_interfaces()->length(); i++) { - assert(ik->local_interfaces()->at(i)->is_loaded(), "must be"); - } - }); - - Handle pd; - PackageEntry* pkg_entry = nullptr; - - // Since a hidden class does not have a name, it cannot be reloaded - // normally via the system dictionary. Instead, we have to finish the - // loading job here. - - if (HeapShared::is_lambda_proxy_klass(ik)) { - InstanceKlass* nest_host = ik->nest_host_not_null(); - assert(nest_host->is_loaded(), "must be"); - pd = Handle(THREAD, nest_host->protection_domain()); - pkg_entry = nest_host->package(); - } - - ik->restore_unshareable_info(loader_data, pd, pkg_entry, CHECK); - SystemDictionary::load_shared_class_misc(ik, loader_data); - ik->add_to_hierarchy(THREAD); - assert(ik->is_loaded(), "Must be in at least loaded state"); - - DEBUG_ONLY({ - // Make sure we don't make this hidden class available by name, even if we don't - // use any special ClassLoaderData. - Handle loader(THREAD, loader_data->class_loader()); - ResourceMark rm(THREAD); - assert(SystemDictionary::resolve_or_null(ik->name(), loader, THREAD) == nullptr, - "hidden classes cannot be accessible by name: %s", ik->external_name()); - if (HAS_PENDING_EXCEPTION) { - CLEAR_PENDING_EXCEPTION; - } - }); -} - -void AOTLinkedClassBulkLoader::finish_loading_javabase_classes(TRAPS) { - init_required_classes_for_loader(Handle(), AOTLinkedClassTable::get()->boot(), CHECK); -} - // Some AOT-linked classes for must be initialized early. This includes // - classes that were AOT-initialized by AOTClassInitializer // - the classes of all objects that are reachable from the archived mirrors of // the AOT-linked classes for . -void AOTLinkedClassBulkLoader::init_required_classes_for_loader(Handle class_loader, Array* classes, TRAPS) { +void AOTLinkedClassBulkLoader::link_or_init_classes_for_loader(Handle class_loader, Array* classes, TRAPS) { if (classes != nullptr) { for (int i = 0; i < classes->length(); i++) { InstanceKlass* ik = classes->at(i); @@ -361,56 +313,6 @@ void AOTLinkedClassBulkLoader::init_required_classes_for_loader(Handle class_loa HeapShared::init_classes_for_special_subgraph(class_loader, CHECK); } -bool AOTLinkedClassBulkLoader::is_pending_aot_linked_class(Klass* k) { - if (!CDSConfig::is_using_aot_linked_classes()) { - return false; - } - - if (_all_completed) { // no more pending aot-linked classes - return false; - } - - if (k->is_objArray_klass()) { - k = ObjArrayKlass::cast(k)->bottom_klass(); - } - if (!k->is_instance_klass()) { - // type array klasses (and their higher dimensions), - // must have been loaded before a GC can ever happen. - return false; - } - - // There's a small window during VM start-up where a not-yet loaded aot-linked - // class k may be discovered by the GC during VM initialization. This can happen - // when the heap contains an aot-cached instance of k, but k is not ready to be - // loaded yet. (TODO: JDK-8342429 eliminates this possibility) - // - // The following checks try to limit this window as much as possible for each of - // the four AOTLinkedClassCategory of classes that can be aot-linked. - - InstanceKlass* ik = InstanceKlass::cast(k); - if (ik->defined_by_boot_loader()) { - if (ik->module() != nullptr && ik->in_javabase_module()) { - // AOTLinkedClassCategory::BOOT1 -- all aot-linked classes in - // java.base must have been loaded before a GC can ever happen. - return false; - } else { - // AOTLinkedClassCategory::BOOT2 classes cannot be loaded until - // module system is ready. - return !_boot2_completed; - } - } else if (ik->defined_by_platform_loader()) { - // AOTLinkedClassCategory::PLATFORM classes cannot be loaded until - // the platform class loader is initialized. - return !_platform_completed; - } else if (ik->defined_by_app_loader()) { - // AOTLinkedClassCategory::APP cannot be loaded until the app class loader - // is initialized. - return !_app_completed; - } else { - return false; - } -} - void AOTLinkedClassBulkLoader::replay_training_at_init(Array* classes, TRAPS) { if (classes != nullptr) { for (int i = 0; i < classes->length(); i++) { @@ -425,7 +327,7 @@ void AOTLinkedClassBulkLoader::replay_training_at_init(Array* cl void AOTLinkedClassBulkLoader::replay_training_at_init_for_preloaded_classes(TRAPS) { if (CDSConfig::is_using_aot_linked_classes() && TrainingData::have_data()) { AOTLinkedClassTable* table = AOTLinkedClassTable::get(); - replay_training_at_init(table->boot(), CHECK); + replay_training_at_init(table->boot1(), CHECK); replay_training_at_init(table->boot2(), CHECK); replay_training_at_init(table->platform(), CHECK); replay_training_at_init(table->app(), CHECK); diff --git a/src/hotspot/share/cds/aotLinkedClassBulkLoader.hpp b/src/hotspot/share/cds/aotLinkedClassBulkLoader.hpp index 95e64a7ddd4..77400a86104 100644 --- a/src/hotspot/share/cds/aotLinkedClassBulkLoader.hpp +++ b/src/hotspot/share/cds/aotLinkedClassBulkLoader.hpp @@ -39,34 +39,40 @@ template class Array; enum class AOTLinkedClassCategory : int; // During a Production Run, the AOTLinkedClassBulkLoader loads all classes from -// a AOTLinkedClassTable into their respective ClassLoaders. This happens very early -// in the JVM bootstrap stage, before any application code is executed. +// the AOTLinkedClassTable into their respective ClassLoaders. This happens very early +// in the JVM bootstrap stage, before any Java bytecode is executed. // +// IMPLEMENTATION NOTES: +// We also proactively link all the classes in the AOTLinkedClassTable, and move +// the AOT-initialized classes to the "initialized" state. Due to limitations +// of the current JVM bootstrap sequence, link_or_init_javabase_classes() and +// link_or_init_non_javabase_classes() need to be called after some Java bytecodes are +// executed. Future RFEs will move these calls to earlier stages. class AOTLinkedClassBulkLoader : AllStatic { - static bool _boot2_completed; - static bool _platform_completed; - static bool _app_completed; - static bool _all_completed; - static void load_classes_in_loader(JavaThread* current, AOTLinkedClassCategory class_category, oop class_loader_oop); - static void load_classes_in_loader_impl(AOTLinkedClassCategory class_category, oop class_loader_oop, TRAPS); - static void load_table(AOTLinkedClassTable* table, AOTLinkedClassCategory class_category, Handle loader, TRAPS); - static void initiate_loading(JavaThread* current, const char* category, Handle initiating_loader, Array* classes); - static void load_classes_impl(AOTLinkedClassCategory class_category, Array* classes, - const char* category_name, Handle loader, TRAPS); - static void load_hidden_class(ClassLoaderData* loader_data, InstanceKlass* ik, TRAPS); - static void init_required_classes_for_loader(Handle class_loader, Array* classes, TRAPS); + static void preload_classes_impl(TRAPS); + static void preload_classes_in_table(Array* classes, + const char* category_name, Handle loader, TRAPS); + static void initiate_loading(JavaThread* current, const char* category, Handle initiating_loader, + Array* classes); + static void link_or_init_non_javabase_classes_impl(TRAPS); + static void link_or_init_classes_for_loader(Handle class_loader, Array* classes, TRAPS); static void replay_training_at_init(Array* classes, TRAPS) NOT_CDS_RETURN; + +#ifdef ASSERT + static void validate_module_of_preloaded_classes(); + static void validate_module_of_preloaded_classes_in_table(Array* classes, + const char* category_name, Handle loader); + static void validate_module(Klass* k, const char* category_name, oop module_oop); +#endif + public: static void serialize(SerializeClosure* soc) NOT_CDS_RETURN; - - static void load_javabase_classes(JavaThread* current) NOT_CDS_RETURN; - static void load_non_javabase_classes(JavaThread* current) NOT_CDS_RETURN; - static void finish_loading_javabase_classes(TRAPS) NOT_CDS_RETURN; + static void preload_classes(JavaThread* current); + static void link_or_init_javabase_classes(JavaThread* current) NOT_CDS_RETURN; + static void link_or_init_non_javabase_classes(JavaThread* current) NOT_CDS_RETURN; static void exit_on_exception(JavaThread* current); static void replay_training_at_init_for_preloaded_classes(TRAPS) NOT_CDS_RETURN; - static bool class_preloading_finished(); - static bool is_pending_aot_linked_class(Klass* k) NOT_CDS_RETURN_(false); }; #endif // SHARE_CDS_AOTLINKEDCLASSBULKLOADER_HPP diff --git a/src/hotspot/share/cds/aotLinkedClassTable.cpp b/src/hotspot/share/cds/aotLinkedClassTable.cpp index 79d78b05be1..a16bdec8740 100644 --- a/src/hotspot/share/cds/aotLinkedClassTable.cpp +++ b/src/hotspot/share/cds/aotLinkedClassTable.cpp @@ -30,7 +30,7 @@ AOTLinkedClassTable AOTLinkedClassTable::_instance; void AOTLinkedClassTable::serialize(SerializeClosure* soc) { - soc->do_ptr((void**)&_boot); + soc->do_ptr((void**)&_boot1); soc->do_ptr((void**)&_boot2); soc->do_ptr((void**)&_platform); soc->do_ptr((void**)&_app); diff --git a/src/hotspot/share/cds/aotLinkedClassTable.hpp b/src/hotspot/share/cds/aotLinkedClassTable.hpp index 0ec733d1df7..a33e53b1d6a 100644 --- a/src/hotspot/share/cds/aotLinkedClassTable.hpp +++ b/src/hotspot/share/cds/aotLinkedClassTable.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -41,26 +41,27 @@ class SerializeClosure; class AOTLinkedClassTable { static AOTLinkedClassTable _instance; - Array* _boot; // only java.base classes - Array* _boot2; // boot classes in other modules + Array* _boot1; // boot classes in java.base module + Array* _boot2; // boot classes in all other (named and unnamed) modules, + // including classes from -Xbootclasspath/a Array* _platform; Array* _app; public: AOTLinkedClassTable() : - _boot(nullptr), _boot2(nullptr), + _boot1(nullptr), _boot2(nullptr), _platform(nullptr), _app(nullptr) {} static AOTLinkedClassTable* get() { return &_instance; } - Array* boot() const { return _boot; } + Array* boot1() const { return _boot1; } Array* boot2() const { return _boot2; } Array* platform() const { return _platform; } Array* app() const { return _app; } - void set_boot (Array* value) { _boot = value; } + void set_boot1 (Array* value) { _boot1 = value; } void set_boot2 (Array* value) { _boot2 = value; } void set_platform(Array* value) { _platform = value; } void set_app (Array* value) { _app = value; } diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index f866461e7d4..6035111416a 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -124,7 +124,7 @@ bool AOTMetaspace::_use_optimized_module_handling = true; // These regions are aligned with AOTMetaspace::core_region_alignment(). // // These 2 regions are populated in the following steps: -// [0] All classes are loaded in AOTMetaspace::preload_classes(). All metadata are +// [0] All classes are loaded in AOTMetaspace::load_classes(). All metadata are // temporarily allocated outside of the shared regions. // [1] We enter a safepoint and allocate a buffer for the rw/ro regions. // [2] C++ vtables are copied into the rw region. @@ -823,12 +823,10 @@ void AOTMetaspace::link_shared_classes(TRAPS) { } } -// Preload classes from a list, populate the shared spaces and dump to a -// file. -void AOTMetaspace::preload_and_dump(TRAPS) { +void AOTMetaspace::dump_static_archive(TRAPS) { CDSConfig::DumperThreadMark dumper_thread_mark(THREAD); ResourceMark rm(THREAD); - HandleMark hm(THREAD); + HandleMark hm(THREAD); if (CDSConfig::is_dumping_final_static_archive() && AOTPrintTrainingInfo) { tty->print_cr("==================== archived_training_data ** before dumping ===================="); @@ -836,7 +834,7 @@ void AOTMetaspace::preload_and_dump(TRAPS) { } StaticArchiveBuilder builder; - preload_and_dump_impl(builder, THREAD); + dump_static_archive_impl(builder, THREAD); if (HAS_PENDING_EXCEPTION) { if (PENDING_EXCEPTION->is_a(vmClasses::OutOfMemoryError_klass())) { aot_log_error(aot)("Out of memory. Please run with a larger Java heap, current MaxHeapSize = " @@ -902,7 +900,7 @@ void AOTMetaspace::get_default_classlist(char* default_classlist, const size_t b Arguments::get_java_home(), filesep, filesep); } -void AOTMetaspace::preload_classes(TRAPS) { +void AOTMetaspace::load_classes(TRAPS) { char default_classlist[JVM_MAXPATHLEN]; const char* classlist_path; @@ -929,8 +927,8 @@ void AOTMetaspace::preload_classes(TRAPS) { } } - // Some classes are used at CDS runtime but are not loaded, and therefore archived, at - // dumptime. We can perform dummmy calls to these classes at dumptime to ensure they + // Some classes are used at CDS runtime but are not yet loaded at this point. + // We can perform dummmy calls to these classes at dumptime to ensure they // are archived. exercise_runtime_cds_code(CHECK); @@ -946,10 +944,10 @@ void AOTMetaspace::exercise_runtime_cds_code(TRAPS) { CDSProtectionDomain::to_file_URL("dummy.jar", Handle(), CHECK); } -void AOTMetaspace::preload_and_dump_impl(StaticArchiveBuilder& builder, TRAPS) { +void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS) { if (CDSConfig::is_dumping_classic_static_archive()) { // We are running with -Xshare:dump - preload_classes(CHECK); + load_classes(CHECK); if (SharedArchiveConfigFile) { log_info(aot)("Reading extra data from %s ...", SharedArchiveConfigFile); diff --git a/src/hotspot/share/cds/aotMetaspace.hpp b/src/hotspot/share/cds/aotMetaspace.hpp index 1803199766d..379c684e939 100644 --- a/src/hotspot/share/cds/aotMetaspace.hpp +++ b/src/hotspot/share/cds/aotMetaspace.hpp @@ -72,15 +72,15 @@ class AOTMetaspace : AllStatic { n_regions = 5 // total number of regions }; - static void preload_and_dump(TRAPS) NOT_CDS_RETURN; + static void dump_static_archive(TRAPS) NOT_CDS_RETURN; #ifdef _LP64 static void adjust_heap_sizes_for_dumping() NOT_CDS_JAVA_HEAP_RETURN; #endif private: static void exercise_runtime_cds_code(TRAPS) NOT_CDS_RETURN; - static void preload_and_dump_impl(StaticArchiveBuilder& builder, TRAPS) NOT_CDS_RETURN; - static void preload_classes(TRAPS) NOT_CDS_RETURN; + static void dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS) NOT_CDS_RETURN; + static void load_classes(TRAPS) NOT_CDS_RETURN; public: static Symbol* symbol_rs_base() { diff --git a/src/hotspot/share/cds/aotOopChecker.cpp b/src/hotspot/share/cds/aotOopChecker.cpp new file mode 100644 index 00000000000..3c0a142a059 --- /dev/null +++ b/src/hotspot/share/cds/aotOopChecker.cpp @@ -0,0 +1,80 @@ +/* + * 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. + * + */ + +#include "cds/aotMetaspace.hpp" +#include "cds/aotOopChecker.hpp" +#include "cds/heapShared.hpp" +#include "classfile/javaClasses.hpp" +#include "classfile/symbolTable.hpp" +#include "classfile/vmClasses.hpp" +#include "oops/instanceKlass.inline.hpp" +#include "runtime/fieldDescriptor.inline.hpp" +#include "utilities/debug.hpp" + +#if INCLUDE_CDS_JAVA_HEAP + +oop AOTOopChecker::get_oop_field(oop obj, const char* name, const char* sig) { + Symbol* name_sym = SymbolTable::probe(name, checked_cast(strlen(name))); + assert(name_sym != nullptr, "Symbol must have been resolved for an existing field of this obj"); + Symbol* sig_sym = SymbolTable::probe(sig, checked_cast(strlen(sig))); + assert(sig_sym != nullptr, "Symbol must have been resolved for an existing field of this obj"); + + fieldDescriptor fd; + Klass* k = InstanceKlass::cast(obj->klass())->find_field(name_sym, sig_sym, &fd); + assert(k != nullptr, "field must exist"); + precond(!fd.is_static()); + precond(fd.field_type() == T_OBJECT || fd.field_type() == T_ARRAY); + return obj->obj_field(fd.offset()); +} + +// Make sure we are not caching objects with assumptions that can be violated in +// the production run. +void AOTOopChecker::check(oop obj) { + // Currently we only check URL objects, but more rules may be added in the future. + + if (obj->klass()->is_subclass_of(vmClasses::URL_klass())) { + // If URL could be subclassed, obj may have new fields that we don't know about. + precond(vmClasses::URL_klass()->is_final()); + + // URLs are referenced by the CodeSources/ProtectDomains that are cached + // for AOT-linked classes loaded by the platform/app loaders. + // + // Do not cache any URLs whose URLStreamHandler can be overridden by the application. + // - "jrt" and "file" will always use the built-in URLStreamHandler. See + // java.net.URL::isOverrideable(). + // - When an AOT-linked class is loaded from a JAR file, its URL is something + // like file:HelloWorl.jar, and does NOT use the "jar" protocol. + oop protocol = get_oop_field(obj, "protocol", "Ljava/lang/String;"); + if (!java_lang_String::equals(protocol, "jrt", 3) && + !java_lang_String::equals(protocol, "file", 4)) { + ResourceMark rm; + log_error(aot)("Must cache only URLs with jrt/file protocols but got: %s", + java_lang_String::as_quoted_ascii(protocol)); + HeapShared::debug_trace(); + AOTMetaspace::unrecoverable_writing_error(); + } + } +} + +#endif //INCLUDE_CDS_JAVA_HEAP diff --git a/src/hotspot/share/cds/aotOopChecker.hpp b/src/hotspot/share/cds/aotOopChecker.hpp new file mode 100644 index 00000000000..1d4b9cd1a75 --- /dev/null +++ b/src/hotspot/share/cds/aotOopChecker.hpp @@ -0,0 +1,40 @@ +/* + * 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. + * + */ + +#ifndef SHARE_CDS_AOTOOPCHECKER_HPP +#define SHARE_CDS_AOTOOPCHECKER_HPP + +#include "memory/allStatic.hpp" +#include "oops/oopsHierarchy.hpp" + +class AOTOopChecker : AllStatic { + static oop get_oop_field(oop obj, const char* name, const char* sig); + +public: + // obj is an object that's about to be stored into the AOT cache. Check if it + // can be safely cached. + static void check(oop obj); +}; + +#endif // SHARE_CDS_AOTOOPCHECKER_HPP diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 5bb46deb9bc..7c6b925470a 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -67,7 +67,8 @@ JavaThread* CDSConfig::_dumper_thread = nullptr; int CDSConfig::get_status() { assert(Universe::is_fully_initialized(), "status is finalized only after Universe is initialized"); - return (is_dumping_archive() ? IS_DUMPING_ARCHIVE : 0) | + return (is_dumping_aot_linked_classes() ? IS_DUMPING_AOT_LINKED_CLASSES : 0) | + (is_dumping_archive() ? IS_DUMPING_ARCHIVE : 0) | (is_dumping_method_handles() ? IS_DUMPING_METHOD_HANDLES : 0) | (is_dumping_static_archive() ? IS_DUMPING_STATIC_ARCHIVE : 0) | (is_logging_lambda_form_invokers() ? IS_LOGGING_LAMBDA_FORM_INVOKERS : 0) | diff --git a/src/hotspot/share/cds/cdsConfig.hpp b/src/hotspot/share/cds/cdsConfig.hpp index cfa846f2b41..d199e97eefd 100644 --- a/src/hotspot/share/cds/cdsConfig.hpp +++ b/src/hotspot/share/cds/cdsConfig.hpp @@ -80,11 +80,12 @@ class CDSConfig : public AllStatic { public: // Used by jdk.internal.misc.CDS.getCDSConfigStatus(); - static const int IS_DUMPING_ARCHIVE = 1 << 0; - static const int IS_DUMPING_METHOD_HANDLES = 1 << 1; - static const int IS_DUMPING_STATIC_ARCHIVE = 1 << 2; - static const int IS_LOGGING_LAMBDA_FORM_INVOKERS = 1 << 3; - static const int IS_USING_ARCHIVE = 1 << 4; + static const int IS_DUMPING_AOT_LINKED_CLASSES = 1 << 0; + static const int IS_DUMPING_ARCHIVE = 1 << 1; + static const int IS_DUMPING_METHOD_HANDLES = 1 << 2; + static const int IS_DUMPING_STATIC_ARCHIVE = 1 << 3; + static const int IS_LOGGING_LAMBDA_FORM_INVOKERS = 1 << 4; + static const int IS_USING_ARCHIVE = 1 << 5; static int get_status() NOT_CDS_RETURN_(0); diff --git a/src/hotspot/share/cds/cdsEnumKlass.cpp b/src/hotspot/share/cds/cdsEnumKlass.cpp index e46a7eb84f4..f771eeec3d7 100644 --- a/src/hotspot/share/cds/cdsEnumKlass.cpp +++ b/src/hotspot/share/cds/cdsEnumKlass.cpp @@ -40,7 +40,9 @@ bool CDSEnumKlass::is_enum_obj(oop orig_obj) { InstanceKlass::cast(k)->is_enum_subclass(); } -// -- Handling of Enum objects +// !!! This is legacy support for enum classes before JEP 483. This file is not used when +// !!! CDSConfig::is_initing_classes_at_dump_time()==true. +// // Java Enum classes have synthetic methods that look like this // enum MyEnum {FOO, BAR} // MyEnum:: { @@ -62,6 +64,7 @@ bool CDSEnumKlass::is_enum_obj(oop orig_obj) { void CDSEnumKlass::handle_enum_obj(int level, KlassSubGraphInfo* subgraph_info, oop orig_obj) { + assert(!CDSConfig::is_initing_classes_at_dump_time(), "only for legacy support of enums"); assert(level > 1, "must never be called at the first (outermost) level"); assert(is_enum_obj(orig_obj), "must be"); diff --git a/src/hotspot/share/cds/cdsEnumKlass.hpp b/src/hotspot/share/cds/cdsEnumKlass.hpp index c898bfec60d..e6019ff705e 100644 --- a/src/hotspot/share/cds/cdsEnumKlass.hpp +++ b/src/hotspot/share/cds/cdsEnumKlass.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -34,6 +34,8 @@ class InstanceKlass; class JavaFieldStream; class KlassSubGraphInfo; +// This is legacy support for enum classes before JEP 483. This code is not needed when +// CDSConfig::is_initing_classes_at_dump_time()==true. class CDSEnumKlass: AllStatic { public: static bool is_enum_obj(oop orig_obj); diff --git a/src/hotspot/share/cds/cdsHeapVerifier.cpp b/src/hotspot/share/cds/cdsHeapVerifier.cpp index 9429a0d8264..3f72a7c6872 100644 --- a/src/hotspot/share/cds/cdsHeapVerifier.cpp +++ b/src/hotspot/share/cds/cdsHeapVerifier.cpp @@ -100,7 +100,7 @@ CDSHeapVerifier::CDSHeapVerifier() : _archived_objs(0), _problems(0) // you might need to fix the core library code, or fix the ADD_EXCL entries below. // // class field type - ADD_EXCL("java/lang/ClassLoader", "scl"); // A + ADD_EXCL("java/lang/ClassLoader$Holder", "scl"); // A ADD_EXCL("java/lang/Module", "ALL_UNNAMED_MODULE", // A "ALL_UNNAMED_MODULE_SET", // A "EVERYONE_MODULE", // A @@ -147,6 +147,10 @@ CDSHeapVerifier::CDSHeapVerifier() : _archived_objs(0), _problems(0) "ZERO"); // D } + if (CDSConfig::is_dumping_aot_linked_classes()) { + ADD_EXCL("java/lang/Package$VersionInfo", "NULL_VERSION_INFO"); // D + } + # undef ADD_EXCL ClassLoaderDataGraph::classes_do(this); @@ -228,10 +232,16 @@ public: return; } - if (field_ik == vmClasses::internal_Unsafe_klass() && ArchiveUtils::has_aot_initialized_mirror(field_ik)) { - // There's only a single instance of jdk/internal/misc/Unsafe, so all references will - // be pointing to this singleton, which has been archived. - return; + if (ArchiveUtils::has_aot_initialized_mirror(field_ik)) { + if (field_ik == vmClasses::internal_Unsafe_klass()) { + // There's only a single instance of jdk/internal/misc/Unsafe, so all references will + // be pointing to this singleton, which has been archived. + return; + } + if (field_ik == vmClasses::Boolean_klass()) { + // TODO: check if is TRUE or FALSE + return; + } } } diff --git a/src/hotspot/share/cds/dynamicArchive.cpp b/src/hotspot/share/cds/dynamicArchive.cpp index dd24f1e0c51..6fac1676b9f 100644 --- a/src/hotspot/share/cds/dynamicArchive.cpp +++ b/src/hotspot/share/cds/dynamicArchive.cpp @@ -438,8 +438,6 @@ void DynamicArchive::setup_array_klasses() { if (_dynamic_archive_array_klasses != nullptr) { for (int i = 0; i < _dynamic_archive_array_klasses->length(); i++) { ObjArrayKlass* oak = _dynamic_archive_array_klasses->at(i); - assert(!oak->is_typeArray_klass(), "all type array classes must be in static archive"); - Klass* elm = oak->element_klass(); assert(AOTMetaspace::in_aot_cache_static_region((void*)elm), "must be"); diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/heapShared.cpp index 92f55ce5b33..de4d1c8a729 100644 --- a/src/hotspot/share/cds/heapShared.cpp +++ b/src/hotspot/share/cds/heapShared.cpp @@ -27,6 +27,7 @@ #include "cds/aotClassLocation.hpp" #include "cds/aotLogging.hpp" #include "cds/aotMetaspace.hpp" +#include "cds/aotOopChecker.hpp" #include "cds/aotReferenceObjSupport.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveHeapLoader.hpp" @@ -325,6 +326,8 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra debug_trace(); return false; } else { + AOTOopChecker::check(obj); // Make sure contents of this oop are safe. + count_allocation(obj->size()); ArchiveHeapWriter::add_source_obj(obj); CachedOopInfo info = make_cached_oop_info(obj, referrer); @@ -612,7 +615,7 @@ void HeapShared::copy_and_rescan_aot_inited_mirror(InstanceKlass* ik) { } } -static void copy_java_mirror_hashcode(oop orig_mirror, oop scratch_m) { +void HeapShared::copy_java_mirror(oop orig_mirror, oop scratch_m) { // We need to retain the identity_hash, because it may have been used by some hashtables // in the shared heap. if (!orig_mirror->fast_no_hash_check()) { @@ -628,6 +631,11 @@ static void copy_java_mirror_hashcode(oop orig_mirror, oop scratch_m) { DEBUG_ONLY(intptr_t archived_hash = scratch_m->identity_hash()); assert(src_hash == archived_hash, "Different hash codes: original " INTPTR_FORMAT ", archived " INTPTR_FORMAT, src_hash, archived_hash); } + + if (CDSConfig::is_dumping_aot_linked_classes()) { + java_lang_Class::set_module(scratch_m, java_lang_Class::module(orig_mirror)); + java_lang_Class::set_protection_domain(scratch_m, java_lang_Class::protection_domain(orig_mirror)); + } } static objArrayOop get_archived_resolved_references(InstanceKlass* src_ik) { @@ -727,7 +735,7 @@ void HeapShared::write_heap(ArchiveHeapInfo *heap_info) { void HeapShared::scan_java_mirror(oop orig_mirror) { oop m = scratch_java_mirror(orig_mirror); if (m != nullptr) { // nullptr if for custom class loader - copy_java_mirror_hashcode(orig_mirror, m); + copy_java_mirror(orig_mirror, m); bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, m); assert(success, "sanity"); } @@ -1638,9 +1646,11 @@ bool HeapShared::walk_one_object(PendingOopStack* stack, int level, KlassSubGrap } if (CDSConfig::is_initing_classes_at_dump_time()) { - // The enum klasses are archived with aot-initialized mirror. - // See AOTClassInitializer::can_archive_initialized_mirror(). + // The classes of all archived enum instances have been marked as aot-init, + // so there's nothing else to be done in the production run. } else { + // This is legacy support for enum classes before JEP 483 -- we cannot rerun + // the enum's in the production run, so special handling is needed. if (CDSEnumKlass::is_enum_obj(orig_obj)) { CDSEnumKlass::handle_enum_obj(level + 1, subgraph_info, orig_obj); } diff --git a/src/hotspot/share/cds/heapShared.hpp b/src/hotspot/share/cds/heapShared.hpp index c9a810a6c0b..c877748fe9c 100644 --- a/src/hotspot/share/cds/heapShared.hpp +++ b/src/hotspot/share/cds/heapShared.hpp @@ -343,6 +343,7 @@ private: static void prepare_resolved_references(); static void archive_strings(); static void archive_subgraphs(); + static void copy_java_mirror(oop orig_mirror, oop scratch_m); // PendingOop and PendingOopStack are used for recursively discovering all cacheable // heap objects. The recursion is done using PendingOopStack so we won't overflow the diff --git a/src/hotspot/share/classfile/classLoaderDataShared.cpp b/src/hotspot/share/classfile/classLoaderDataShared.cpp index a495327864d..2a59a3339b6 100644 --- a/src/hotspot/share/classfile/classLoaderDataShared.cpp +++ b/src/hotspot/share/classfile/classLoaderDataShared.cpp @@ -280,4 +280,17 @@ void ClassLoaderDataShared::restore_java_system_loader_from_archive(ClassLoaderD _full_module_graph_loaded = true; } +// This is called before AOTLinkedClassBulkLoader starts preloading classes. It makes sure that +// when we preload any class, its module is already valid. +void ClassLoaderDataShared::restore_archived_modules_for_preloading_classes(JavaThread* current) { + precond(CDSConfig::is_using_aot_linked_classes()); + + precond(_platform_loader_root_index >= 0); + precond(_system_loader_root_index >= 0); + + Handle h_platform_loader(current, HeapShared::get_root(_platform_loader_root_index)); + Handle h_system_loader(current, HeapShared::get_root(_system_loader_root_index)); + Modules::init_archived_modules(current, h_platform_loader, h_system_loader); +} + #endif // INCLUDE_CDS_JAVA_HEAP diff --git a/src/hotspot/share/classfile/classLoaderDataShared.hpp b/src/hotspot/share/classfile/classLoaderDataShared.hpp index 6ef338f0f34..944d415af5c 100644 --- a/src/hotspot/share/classfile/classLoaderDataShared.hpp +++ b/src/hotspot/share/classfile/classLoaderDataShared.hpp @@ -27,6 +27,7 @@ #include "memory/allStatic.hpp" #include "oops/oopsHierarchy.hpp" +#include "utilities/macros.hpp" class ClassLoaderData; class MetaspaceClosure; @@ -37,6 +38,7 @@ class ClassLoaderDataShared : AllStatic { static bool _full_module_graph_loaded; CDS_JAVA_HEAP_ONLY(static void ensure_module_entry_table_exists(oop class_loader);) public: + static void restore_archived_modules_for_preloading_classes(JavaThread* current) NOT_CDS_JAVA_HEAP_RETURN; #if INCLUDE_CDS_JAVA_HEAP static void ensure_module_entry_tables_exist(); static void allocate_archived_tables(); diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index 60a63892518..75f54bfa549 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -1013,12 +1013,25 @@ void java_lang_Class::initialize_mirror_fields(InstanceKlass* ik, // Set the java.lang.Module module field in the java_lang_Class mirror void java_lang_Class::set_mirror_module_field(JavaThread* current, Klass* k, Handle mirror, Handle module) { + if (CDSConfig::is_using_aot_linked_classes()) { + oop archived_module = java_lang_Class::module(mirror()); + if (archived_module != nullptr) { + precond(module() == nullptr || module() == archived_module); + precond(AOTMetaspace::in_aot_cache_static_region((void*)k)); + return; + } + } + if (module.is_null()) { // During startup, the module may be null only if java.base has not been defined yet. // Put the class on the fixup_module_list to patch later when the java.lang.Module // for java.base is known. But note that since we captured the null module another // thread may have completed that initialization. + // With AOT-linked classes, java.base should have been defined before the + // VM loads any classes. + precond(!CDSConfig::is_using_aot_linked_classes()); + bool javabase_was_defined = false; { MutexLocker m1(current, Module_lock); @@ -1052,13 +1065,19 @@ void java_lang_Class::set_mirror_module_field(JavaThread* current, Klass* k, Han // Statically allocate fixup lists because they always get created. void java_lang_Class::allocate_fixup_lists() { - GrowableArray* mirror_list = - new (mtClass) GrowableArray(40, mtClass); - set_fixup_mirror_list(mirror_list); + if (!CDSConfig::is_using_aot_linked_classes()) { + // fixup_mirror_list() is not used when we have preloaded classes. See + // Universe::fixup_mirrors(). + GrowableArray* mirror_list = + new (mtClass) GrowableArray(40, mtClass); + set_fixup_mirror_list(mirror_list); - GrowableArray* module_list = - new (mtModule) GrowableArray(500, mtModule); - set_fixup_module_field_list(module_list); + // With AOT-linked classes, java.base module is defined before any class + // is loaded, so there's no need for fixup_module_field_list(). + GrowableArray* module_list = + new (mtModule) GrowableArray(500, mtModule); + set_fixup_module_field_list(module_list); + } } void java_lang_Class::allocate_mirror(Klass* k, bool is_scratch, Handle protection_domain, Handle classData, @@ -1158,6 +1177,7 @@ void java_lang_Class::create_mirror(Klass* k, Handle class_loader, create_scratch_mirror(k, CHECK); } } else { + assert(!CDSConfig::is_using_aot_linked_classes(), "should not come here"); assert(fixup_mirror_list() != nullptr, "fixup_mirror_list not initialized"); fixup_mirror_list()->push(k); } @@ -1203,7 +1223,7 @@ bool java_lang_Class::restore_archived_mirror(Klass *k, Handle protection_domain, TRAPS) { // Postpone restoring archived mirror until java.lang.Class is loaded. Please // see more details in vmClasses::resolve_all(). - if (!vmClasses::Class_klass_loaded()) { + if (!vmClasses::Class_klass_loaded() && !CDSConfig::is_using_aot_linked_classes()) { assert(fixup_mirror_list() != nullptr, "fixup_mirror_list not initialized"); fixup_mirror_list()->push(k); return true; diff --git a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp index b137f1a8035..750f27fcf47 100644 --- a/src/hotspot/share/classfile/javaClasses.hpp +++ b/src/hotspot/share/classfile/javaClasses.hpp @@ -234,6 +234,7 @@ class java_lang_String : AllStatic { class java_lang_Class : AllStatic { friend class VMStructs; friend class JVMCIVMStructs; + friend class HeapShared; private: diff --git a/src/hotspot/share/classfile/moduleEntry.cpp b/src/hotspot/share/classfile/moduleEntry.cpp index ce887081cdb..88b59540d04 100644 --- a/src/hotspot/share/classfile/moduleEntry.cpp +++ b/src/hotspot/share/classfile/moduleEntry.cpp @@ -697,6 +697,8 @@ void ModuleEntryTable::finalize_javabase(Handle module_handle, Symbol* version, // classes needing their module field set are added to the fixup_module_list. // Their module field is set once java.base's java.lang.Module is known to the VM. void ModuleEntryTable::patch_javabase_entries(JavaThread* current, Handle module_handle) { + assert(!CDSConfig::is_using_aot_linked_classes(), "patching is not necessary with AOT-linked classes"); + if (module_handle.is_null()) { fatal("Unable to patch the module field of classes loaded prior to " JAVA_BASE_NAME "'s definition, invalid java.lang.Module"); diff --git a/src/hotspot/share/classfile/modules.cpp b/src/hotspot/share/classfile/modules.cpp index 062521e7495..522d1d0842d 100644 --- a/src/hotspot/share/classfile/modules.cpp +++ b/src/hotspot/share/classfile/modules.cpp @@ -700,6 +700,26 @@ void Modules::serialize_archived_module_info(SerializeClosure* soc) { void Modules::define_archived_modules(Handle h_platform_loader, Handle h_system_loader, TRAPS) { assert(CDSConfig::is_using_full_module_graph(), "must be"); + if (h_platform_loader.is_null()) { + THROW_MSG(vmSymbols::java_lang_NullPointerException(), "Null platform loader object"); + } + + if (h_system_loader.is_null()) { + THROW_MSG(vmSymbols::java_lang_NullPointerException(), "Null system loader object"); + } + + if (CDSConfig::is_using_aot_linked_classes()) { + // Already initialized + precond(SystemDictionary::java_platform_loader() == h_platform_loader()); + precond(SystemDictionary::java_system_loader() == h_system_loader()); + } else { + init_archived_modules(THREAD, h_platform_loader, h_system_loader); + } +} + +void Modules::init_archived_modules(JavaThread* current, Handle h_platform_loader, Handle h_system_loader) { + assert(CDSConfig::is_using_full_module_graph(), "must be"); + ExceptionMark em(current); // We don't want the classes used by the archived full module graph to be redefined by JVMTI. // Luckily, such classes are loaded in the JVMTI "early" phase, and CDS is disabled if a JVMTI @@ -708,16 +728,15 @@ void Modules::define_archived_modules(Handle h_platform_loader, Handle h_system_ assert(!(JvmtiExport::should_post_class_file_load_hook() && JvmtiExport::has_early_class_hook_env()), "CDS should be disabled if early class hooks are enabled"); - Handle java_base_module(THREAD, ClassLoaderDataShared::restore_archived_oops_for_null_class_loader_data()); - // Patch any previously loaded class's module field with java.base's java.lang.Module. - ModuleEntryTable::patch_javabase_entries(THREAD, java_base_module); - - if (h_platform_loader.is_null()) { - THROW_MSG(vmSymbols::java_lang_NullPointerException(), "Null platform loader object"); + if (CDSConfig::is_using_aot_linked_classes()) { + ClassLoaderData* boot_loader_data = ClassLoaderData::the_null_class_loader_data(); + ClassLoaderDataShared::archived_boot_unnamed_module()->restore_archived_oops(boot_loader_data); } - if (h_system_loader.is_null()) { - THROW_MSG(vmSymbols::java_lang_NullPointerException(), "Null system loader object"); + Handle java_base_module(current, ClassLoaderDataShared::restore_archived_oops_for_null_class_loader_data()); + if (!CDSConfig::is_using_aot_linked_classes()) { + // Patch any previously loaded class's module field with java.base's java.lang.Module. + ModuleEntryTable::patch_javabase_entries(current, java_base_module); } ClassLoaderData* platform_loader_data = SystemDictionary::register_loader(h_platform_loader); @@ -777,7 +796,9 @@ void Modules::set_bootloader_unnamed_module(Handle module, TRAPS) { #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_using_full_module_graph()) { precond(unnamed_module == ClassLoaderDataShared::archived_boot_unnamed_module()); - unnamed_module->restore_archived_oops(boot_loader_data); + if (!CDSConfig::is_using_aot_linked_classes()) { + unnamed_module->restore_archived_oops(boot_loader_data); + } } else #endif { diff --git a/src/hotspot/share/classfile/modules.hpp b/src/hotspot/share/classfile/modules.hpp index d6d81263449..27a22c1017a 100644 --- a/src/hotspot/share/classfile/modules.hpp +++ b/src/hotspot/share/classfile/modules.hpp @@ -57,6 +57,8 @@ public: static void check_archived_module_oop(oop orig_module_obj) NOT_CDS_JAVA_HEAP_RETURN; static void define_archived_modules(Handle h_platform_loader, Handle h_system_loader, TRAPS) NOT_CDS_JAVA_HEAP_RETURN; + static void init_archived_modules(JavaThread* current, Handle h_platform_loader, Handle h_system_loader) + NOT_CDS_JAVA_HEAP_RETURN; static void verify_archived_modules() NOT_CDS_JAVA_HEAP_RETURN; static void dump_archived_module_info() NOT_CDS_JAVA_HEAP_RETURN; static void serialize_archived_module_info(SerializeClosure* soc) NOT_CDS_JAVA_HEAP_RETURN; diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp index 95f86a8950b..2f7001c86b3 100644 --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -196,14 +196,19 @@ ClassLoaderData* SystemDictionary::register_loader(Handle class_loader, bool cre } void SystemDictionary::set_system_loader(ClassLoaderData *cld) { - assert(_java_system_loader.is_empty(), "already set!"); - _java_system_loader = cld->class_loader_handle(); - + if (_java_system_loader.is_empty()) { + _java_system_loader = cld->class_loader_handle(); + } else { + assert(_java_system_loader.resolve() == cld->class_loader(), "sanity"); + } } void SystemDictionary::set_platform_loader(ClassLoaderData *cld) { - assert(_java_platform_loader.is_empty(), "already set!"); - _java_platform_loader = cld->class_loader_handle(); + if (_java_platform_loader.is_empty()) { + _java_platform_loader = cld->class_loader_handle(); + } else { + assert(_java_platform_loader.resolve() == cld->class_loader(), "sanity"); + } } // ---------------------------------------------------------------------------- @@ -1149,6 +1154,58 @@ void SystemDictionary::load_shared_class_misc(InstanceKlass* ik, ClassLoaderData } } +// This is much more lightweight than SystemDictionary::resolve_or_null +// - There's only a single Java thread at this point. No need for placeholder. +// - All supertypes of ik have been loaded +// - There's no circularity (checked in AOT assembly phase) +// - There's no need to call java.lang.ClassLoader::load_class() because the boot/platform/app +// loaders are well-behaved +void SystemDictionary::preload_class(Handle class_loader, InstanceKlass* ik, TRAPS) { + precond(Universe::is_bootstrapping()); + precond(java_platform_loader() != nullptr && java_system_loader() != nullptr); + precond(class_loader() == nullptr || class_loader() == java_platform_loader() ||class_loader() == java_system_loader()); + precond(CDSConfig::is_using_aot_linked_classes()); + precond(AOTMetaspace::in_aot_cache_static_region((void*)ik)); + precond(!ik->is_loaded()); + +#ifdef ASSERT + // preload_class() must be called in the correct order -- all super types must have + // already been loaded. + if (ik->java_super() != nullptr) { + assert(ik->java_super()->is_loaded(), "must be"); + } + + Array* interfaces = ik->local_interfaces(); + int num_interfaces = interfaces->length(); + for (int index = 0; index < num_interfaces; index++) { + assert(interfaces->at(index)->is_loaded(), "must be"); + } +#endif + + ClassLoaderData* loader_data = ClassLoaderData::class_loader_data(class_loader()); + oop java_mirror = ik->archived_java_mirror(); + precond(java_mirror != nullptr); + + Handle pd(THREAD, java_lang_Class::protection_domain(java_mirror)); + PackageEntry* pkg_entry = ik->package(); + assert(pkg_entry != nullptr || ClassLoader::package_from_class_name(ik->name()) == nullptr, + "non-empty packages must have been archived"); + + // TODO: the following assert requires JDK-8365580 + // assert(is_shared_class_visible(ik->name(), ik, pkg_entry, class_loader), "must be"); + + ik->restore_unshareable_info(loader_data, pd, pkg_entry, CHECK); + load_shared_class_misc(ik, loader_data); + ik->add_to_hierarchy(THREAD); + + if (!ik->is_hidden()) { + update_dictionary(THREAD, ik, loader_data); + } + + assert(java_lang_Class::module(java_mirror) != nullptr, "must have been archived"); + assert(ik->is_loaded(), "Must be in at least loaded state"); +} + #endif // INCLUDE_CDS InstanceKlass* SystemDictionary::load_instance_class_impl(Symbol* class_name, Handle class_loader, TRAPS) { diff --git a/src/hotspot/share/classfile/systemDictionary.hpp b/src/hotspot/share/classfile/systemDictionary.hpp index 8cf2cd83b82..99cb1d0b5d2 100644 --- a/src/hotspot/share/classfile/systemDictionary.hpp +++ b/src/hotspot/share/classfile/systemDictionary.hpp @@ -326,7 +326,7 @@ private: static void restore_archived_method_handle_intrinsics_impl(TRAPS) NOT_CDS_RETURN; protected: - // Used by SystemDictionaryShared and LambdaProxyClassDictionary + // Used by AOTLinkedClassBulkLoader, LambdaProxyClassDictionary, and SystemDictionaryShared static bool add_loader_constraint(Symbol* name, Klass* klass_being_linked, Handle loader1, Handle loader2); @@ -337,6 +337,7 @@ protected: const ClassFileStream *cfs, PackageEntry* pkg_entry, TRAPS); + static void preload_class(Handle class_loader, InstanceKlass* ik, TRAPS); static Handle get_loader_lock_or_null(Handle class_loader); static InstanceKlass* find_or_define_instance_class(Symbol* class_name, Handle class_loader, diff --git a/src/hotspot/share/classfile/vmClasses.cpp b/src/hotspot/share/classfile/vmClasses.cpp index 23bc054755a..e337d5569bc 100644 --- a/src/hotspot/share/classfile/vmClasses.cpp +++ b/src/hotspot/share/classfile/vmClasses.cpp @@ -101,7 +101,11 @@ bool vmClasses::resolve(vmClassID id, TRAPS) { void vmClasses::resolve_until(vmClassID limit_id, vmClassID &start_id, TRAPS) { assert((int)start_id <= (int)limit_id, "IDs are out of order!"); for (auto id : EnumRange{start_id, limit_id}) { // (inclusive start, exclusive end) - resolve(id, CHECK); + if (CDSConfig::is_using_aot_linked_classes()) { + precond(klass_at(id)->is_loaded()); + } else { + resolve(id, CHECK); + } } // move the starting value forward to the limit: @@ -115,6 +119,10 @@ void vmClasses::resolve_all(TRAPS) { // after vmSymbols::initialize() is called but before any classes are pre-loaded. ClassLoader::classLoader_init2(THREAD); + if (CDSConfig::is_using_aot_linked_classes()) { + AOTLinkedClassBulkLoader::preload_classes(THREAD); + } + // Preload commonly used klasses vmClassID scan = vmClassID::FIRST; // first do Object, then String, Class @@ -210,9 +218,6 @@ void vmClasses::resolve_all(TRAPS) { #endif InstanceStackChunkKlass::init_offset_of_stack(); - if (CDSConfig::is_using_aot_linked_classes()) { - AOTLinkedClassBulkLoader::load_javabase_classes(THREAD); - } } #if INCLUDE_CDS diff --git a/src/hotspot/share/compiler/compilationPolicy.cpp b/src/hotspot/share/compiler/compilationPolicy.cpp index c91d299510d..177fd04fbbc 100644 --- a/src/hotspot/share/compiler/compilationPolicy.cpp +++ b/src/hotspot/share/compiler/compilationPolicy.cpp @@ -852,13 +852,6 @@ nmethod* CompilationPolicy::event(const methodHandle& method, const methodHandle print_event(bci == InvocationEntryBci ? CALL : LOOP, method(), inlinee(), bci, comp_level); } -#if INCLUDE_JVMCI - if (EnableJVMCI && UseJVMCICompiler && - comp_level == CompLevel_full_optimization CDS_ONLY(&& !AOTLinkedClassBulkLoader::class_preloading_finished())) { - return nullptr; - } -#endif - if (comp_level == CompLevel_none && JvmtiExport::can_post_interpreter_events() && THREAD->is_interp_only_mode()) { @@ -1452,12 +1445,7 @@ CompLevel CompilationPolicy::call_event(const methodHandle& method, CompLevel cu } else { next_level = MAX2(osr_level, next_level); } -#if INCLUDE_JVMCI - if (EnableJVMCI && UseJVMCICompiler && - next_level == CompLevel_full_optimization CDS_ONLY(&& !AOTLinkedClassBulkLoader::class_preloading_finished())) { - next_level = cur_level; - } -#endif + return next_level; } diff --git a/src/hotspot/share/memory/iterator.inline.hpp b/src/hotspot/share/memory/iterator.inline.hpp index 498c74fd1d2..2975e050b70 100644 --- a/src/hotspot/share/memory/iterator.inline.hpp +++ b/src/hotspot/share/memory/iterator.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 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 @@ -27,7 +27,6 @@ #include "memory/iterator.hpp" -#include "cds/aotLinkedClassBulkLoader.hpp" #include "classfile/classLoaderData.hpp" #include "code/nmethod.hpp" #include "oops/access.inline.hpp" @@ -51,12 +50,7 @@ inline void ClaimMetadataVisitingOopIterateClosure::do_cld(ClassLoaderData* cld) } inline void ClaimMetadataVisitingOopIterateClosure::do_klass(Klass* k) { - ClassLoaderData* cld = k->class_loader_data(); - if (cld != nullptr) { - ClaimMetadataVisitingOopIterateClosure::do_cld(cld); - } else { - assert(AOTLinkedClassBulkLoader::is_pending_aot_linked_class(k), "sanity"); - } + ClaimMetadataVisitingOopIterateClosure::do_cld(k->class_loader_data()); } inline void ClaimMetadataVisitingOopIterateClosure::do_nmethod(nmethod* nm) { diff --git a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp index a3afcc5ba64..a3b841a400b 100644 --- a/src/hotspot/share/memory/universe.cpp +++ b/src/hotspot/share/memory/universe.cpp @@ -584,6 +584,11 @@ void Universe::initialize_basic_type_mirrors(TRAPS) { } void Universe::fixup_mirrors(TRAPS) { + if (CDSConfig::is_using_aot_linked_classes()) { + // All mirrors of preloaded classes are already restored. No need to fix up. + return; + } + // Bootstrap problem: all classes gets a mirror (java.lang.Class instance) assigned eagerly, // but we cannot do that for classes created before java.lang.Class is loaded. Here we simply // walk over permanent objects created so far (mostly classes) and fixup their mirrors. Note diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index 997dd1f802a..9363f9055ba 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -450,7 +450,7 @@ void before_exit(JavaThread* thread, bool halt) { ClassListWriter::write_resolved_constants(); if (CDSConfig::is_dumping_preimage_static_archive()) { - AOTMetaspace::preload_and_dump(thread); + AOTMetaspace::dump_static_archive(thread); } #endif diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index 65eea9d5fb2..0172fe4d69b 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -772,7 +772,7 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { if (CDSConfig::is_using_aot_linked_classes()) { SystemDictionary::restore_archived_method_handle_intrinsics(); - AOTLinkedClassBulkLoader::finish_loading_javabase_classes(CHECK_JNI_ERR); + AOTLinkedClassBulkLoader::link_or_init_javabase_classes(THREAD); } // Start string deduplication thread if requested. @@ -791,7 +791,7 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { call_initPhase2(CHECK_JNI_ERR); if (CDSConfig::is_using_aot_linked_classes()) { - AOTLinkedClassBulkLoader::load_non_javabase_classes(THREAD); + AOTLinkedClassBulkLoader::link_or_init_non_javabase_classes(THREAD); } #ifndef PRODUCT HeapShared::initialize_test_class_from_archive(THREAD); @@ -889,10 +889,10 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { if (CDSConfig::is_dumping_classic_static_archive()) { // Classic -Xshare:dump, aka "old workflow" - AOTMetaspace::preload_and_dump(CHECK_JNI_ERR); + AOTMetaspace::dump_static_archive(CHECK_JNI_ERR); } else if (CDSConfig::is_dumping_final_static_archive()) { tty->print_cr("Reading AOTConfiguration %s and writing AOTCache %s", AOTConfiguration, AOTCache); - AOTMetaspace::preload_and_dump(CHECK_JNI_ERR); + AOTMetaspace::dump_static_archive(CHECK_JNI_ERR); } if (log_is_enabled(Info, perf, class, link)) { diff --git a/src/java.base/share/classes/java/lang/ClassLoader.java b/src/java.base/share/classes/java/lang/ClassLoader.java index 511785a5ac8..47f624cb0f8 100644 --- a/src/java.base/share/classes/java/lang/ClassLoader.java +++ b/src/java.base/share/classes/java/lang/ClassLoader.java @@ -59,12 +59,15 @@ import jdk.internal.loader.ClassLoaders; import jdk.internal.loader.NativeLibrary; import jdk.internal.loader.NativeLibraries; import jdk.internal.perf.PerfCounter; +import jdk.internal.misc.CDS; import jdk.internal.misc.Unsafe; import jdk.internal.misc.VM; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.CallerSensitiveAdapter; import jdk.internal.reflect.Reflection; import jdk.internal.util.StaticProperty; +import jdk.internal.vm.annotation.AOTRuntimeSetup; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; /** * A class loader is an object that is responsible for loading classes. The @@ -221,10 +224,16 @@ import jdk.internal.util.StaticProperty; * @see #resolveClass(Class) * @since 1.0 */ +@AOTSafeClassInitializer public abstract class ClassLoader { private static native void registerNatives(); static { + runtimeSetup(); + } + + @AOTRuntimeSetup + private static void runtimeSetup() { registerNatives(); } @@ -1858,8 +1867,8 @@ public abstract class ClassLoader { throw new IllegalStateException(msg); default: // system fully initialized - assert VM.isBooted() && scl != null; - return scl; + assert VM.isBooted() && Holder.scl != null; + return Holder.scl; } } @@ -1884,7 +1893,7 @@ public abstract class ClassLoader { } // detect recursive initialization - if (scl != null) { + if (Holder.scl != null) { throw new IllegalStateException("recursive invocation"); } @@ -1895,7 +1904,7 @@ public abstract class ClassLoader { // custom class loader is only supported to be loaded from unnamed module Constructor ctor = Class.forName(cn, false, builtinLoader) .getDeclaredConstructor(ClassLoader.class); - scl = (ClassLoader) ctor.newInstance(builtinLoader); + Holder.scl = (ClassLoader) ctor.newInstance(builtinLoader); } catch (Exception e) { Throwable cause = e; if (e instanceof InvocationTargetException) { @@ -1910,9 +1919,9 @@ public abstract class ClassLoader { throw new Error(cause.getMessage(), cause); } } else { - scl = builtinLoader; + Holder.scl = builtinLoader; } - return scl; + return Holder.scl; } // Returns the class's class loader, or null if none. @@ -1925,9 +1934,13 @@ public abstract class ClassLoader { return caller.getClassLoader0(); } - // The system class loader - // @GuardedBy("ClassLoader.class") - private static volatile ClassLoader scl; + // Holder has the field(s) that need to be initialized during JVM bootstrap even if + // the outer is aot-initialized. + private static class Holder { + // The system class loader + // @GuardedBy("ClassLoader.class") + private static volatile ClassLoader scl; + } // -- Package -- @@ -2602,7 +2615,21 @@ public abstract class ClassLoader { if (parallelLockMap != null) { reinitObjectField("parallelLockMap", new ConcurrentHashMap<>()); } - reinitObjectField("packages", new ConcurrentHashMap<>()); + + if (CDS.isDumpingAOTLinkedClasses()) { + if (System.getProperty("cds.debug.archived.packages") != null) { + for (Map.Entry entry : packages.entrySet()) { + String key = entry.getKey(); + NamedPackage value = entry.getValue(); + System.out.println("Archiving " + + (value instanceof Package ? "Package" : "NamedPackage") + + " \"" + key + "\" for " + this); + } + } + } else { + reinitObjectField("packages", new ConcurrentHashMap<>()); + } + reinitObjectField("package2certs", new ConcurrentHashMap<>()); classes.clear(); classes.trimToSize(); diff --git a/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java b/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java index c55ccd735df..d5f4470d9c9 100644 --- a/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java +++ b/src/java.base/share/classes/java/lang/module/ModuleDescriptor.java @@ -54,6 +54,8 @@ import static java.util.Objects.*; import jdk.internal.module.Checks; import jdk.internal.module.ModuleInfo; +import jdk.internal.vm.annotation.AOTRuntimeSetup; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; /** @@ -91,6 +93,7 @@ import jdk.internal.module.ModuleInfo; * @since 9 */ +@AOTSafeClassInitializer public final class ModuleDescriptor implements Comparable { @@ -2665,6 +2668,11 @@ public final class ModuleDescriptor } static { + runtimeSetup(); + } + + @AOTRuntimeSetup + private static void runtimeSetup() { /** * Setup the shared secret to allow code in other packages access * private package methods in java.lang.module. diff --git a/src/java.base/share/classes/java/net/URI.java b/src/java.base/share/classes/java/net/URI.java index daf63d19032..d130dc3b460 100644 --- a/src/java.base/share/classes/java/net/URI.java +++ b/src/java.base/share/classes/java/net/URI.java @@ -43,6 +43,8 @@ import java.text.Normalizer; import jdk.internal.access.JavaNetUriAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.util.Exceptions; +import jdk.internal.vm.annotation.AOTRuntimeSetup; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import sun.nio.cs.UTF_8; import static jdk.internal.util.Exceptions.filterNonSocketInfo; @@ -516,6 +518,7 @@ import static jdk.internal.util.Exceptions.formatMsg; * @see URISyntaxException */ +@AOTSafeClassInitializer public final class URI implements Comparable, Serializable { @@ -3726,7 +3729,13 @@ public final class URI } } + static { + runtimeSetup(); + } + + @AOTRuntimeSetup + private static void runtimeSetup() { SharedSecrets.setJavaNetUriAccess( new JavaNetUriAccess() { public URI create(String scheme, String path) { diff --git a/src/java.base/share/classes/java/net/URL.java b/src/java.base/share/classes/java/net/URL.java index 9266b6c94f1..1435d851f41 100644 --- a/src/java.base/share/classes/java/net/URL.java +++ b/src/java.base/share/classes/java/net/URL.java @@ -43,6 +43,8 @@ import jdk.internal.access.JavaNetURLAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.misc.ThreadTracker; import jdk.internal.misc.VM; +import jdk.internal.vm.annotation.AOTRuntimeSetup; +import jdk.internal.vm.annotation.AOTSafeClassInitializer; import sun.net.util.IPAddressUtil; import static jdk.internal.util.Exceptions.filterNonSocketInfo; import static jdk.internal.util.Exceptions.formatMsg; @@ -214,6 +216,7 @@ import static jdk.internal.util.Exceptions.formatMsg; * @author James Gosling * @since 1.0 */ +@AOTSafeClassInitializer public final class URL implements java.io.Serializable { static final String BUILTIN_HANDLERS_PREFIX = "sun.net.www.protocol"; @@ -1758,6 +1761,11 @@ public final class URL implements java.io.Serializable { } static { + runtimeSetup(); + } + + @AOTRuntimeSetup + private static void runtimeSetup() { SharedSecrets.setJavaNetURLAccess( new JavaNetURLAccess() { @Override diff --git a/src/java.base/share/classes/java/security/SecureClassLoader.java b/src/java.base/share/classes/java/security/SecureClassLoader.java index b398d7332d7..7b0420ec601 100644 --- a/src/java.base/share/classes/java/security/SecureClassLoader.java +++ b/src/java.base/share/classes/java/security/SecureClassLoader.java @@ -29,6 +29,7 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; +import jdk.internal.misc.CDS; /** * This class extends {@code ClassLoader} with additional support for defining @@ -243,6 +244,20 @@ public class SecureClassLoader extends ClassLoader { * Called by the VM, during -Xshare:dump */ private void resetArchivedStates() { - pdcache.clear(); + if (CDS.isDumpingAOTLinkedClasses()) { + for (CodeSourceKey key : pdcache.keySet()) { + if (key.cs.getCodeSigners() != null) { + // We don't archive any signed classes, so we don't need to cache their ProtectionDomains. + pdcache.remove(key); + } + } + if (System.getProperty("cds.debug.archived.protection.domains") != null) { + for (CodeSourceKey key : pdcache.keySet()) { + System.out.println("Archiving ProtectionDomain " + key.cs + " for " + this); + } + } + } else { + pdcache.clear(); + } } } diff --git a/src/java.base/share/classes/jdk/internal/loader/BootLoader.java b/src/java.base/share/classes/jdk/internal/loader/BootLoader.java index bc5bd9d4265..72c7e7e7451 100644 --- a/src/java.base/share/classes/jdk/internal/loader/BootLoader.java +++ b/src/java.base/share/classes/jdk/internal/loader/BootLoader.java @@ -75,9 +75,13 @@ public class BootLoader { private static final ConcurrentHashMap CLASS_LOADER_VALUE_MAP = new ConcurrentHashMap<>(); - // native libraries loaded by the boot class loader - private static final NativeLibraries NATIVE_LIBS - = NativeLibraries.newInstance(null); + // Holder has the field(s) that need to be initialized during JVM bootstrap even if + // the outer is aot-initialized. + private static class Holder { + // native libraries loaded by the boot class loader + private static final NativeLibraries NATIVE_LIBS + = NativeLibraries.newInstance(null); + } /** * Returns the unnamed module for the boot loader. @@ -104,7 +108,7 @@ public class BootLoader { * Returns NativeLibraries for the boot class loader. */ public static NativeLibraries getNativeLibraries() { - return NATIVE_LIBS; + return Holder.NATIVE_LIBS; } /** diff --git a/src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java b/src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java index 44eaab0e83a..98cedb0b3bf 100644 --- a/src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java +++ b/src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 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 @@ -153,7 +153,7 @@ public final class NativeLibraries { } // cannot be loaded by other class loaders - if (loadedLibraryNames.contains(name)) { + if (Holder.loadedLibraryNames.contains(name)) { throw new UnsatisfiedLinkError("Native Library " + name + " already loaded in another classloader"); } @@ -203,7 +203,7 @@ public final class NativeLibraries { NativeLibraryContext.pop(); } // register the loaded native library - loadedLibraryNames.add(name); + Holder.loadedLibraryNames.add(name); libraries.put(name, lib); return lib; } finally { @@ -243,6 +243,11 @@ public final class NativeLibraries { return lib; } + // Called at the end of AOTCache assembly phase. + public void clear() { + libraries.clear(); + } + private NativeLibrary findFromPaths(String[] paths, Class fromClass, String name) { for (String path : paths) { File libfile = new File(path, System.mapLibraryName(name)); @@ -368,7 +373,7 @@ public final class NativeLibraries { acquireNativeLibraryLock(name); try { /* remove the native library name */ - if (!loadedLibraryNames.remove(name)) { + if (!Holder.loadedLibraryNames.remove(name)) { throw new IllegalStateException(name + " has already been unloaded"); } NativeLibraryContext.push(UNLOADER); @@ -395,9 +400,13 @@ public final class NativeLibraries { static final String[] USER_PATHS = ClassLoaderHelper.parsePath(StaticProperty.javaLibraryPath()); } - // All native libraries we've loaded. - private static final Set loadedLibraryNames = + // Holder has the fields that need to be initialized during JVM bootstrap even if + // the outer is aot-initialized. + static class Holder { + // All native libraries we've loaded. + private static final Set loadedLibraryNames = ConcurrentHashMap.newKeySet(); + } // reentrant lock class that allows exact counting (with external synchronization) @SuppressWarnings("serial") diff --git a/src/java.base/share/classes/jdk/internal/misc/CDS.java b/src/java.base/share/classes/jdk/internal/misc/CDS.java index 72b8479de9a..b61743c1fb3 100644 --- a/src/java.base/share/classes/jdk/internal/misc/CDS.java +++ b/src/java.base/share/classes/jdk/internal/misc/CDS.java @@ -47,11 +47,13 @@ import jdk.internal.util.StaticProperty; public class CDS { // Must be in sync with cdsConfig.hpp - private static final int IS_DUMPING_ARCHIVE = 1 << 0; - private static final int IS_DUMPING_METHOD_HANDLES = 1 << 1; - private static final int IS_DUMPING_STATIC_ARCHIVE = 1 << 2; - private static final int IS_LOGGING_LAMBDA_FORM_INVOKERS = 1 << 3; - private static final int IS_USING_ARCHIVE = 1 << 4; + private static final int IS_DUMPING_AOT_LINKED_CLASSES = 1 << 0; + private static final int IS_DUMPING_ARCHIVE = 1 << 1; + private static final int IS_DUMPING_METHOD_HANDLES = 1 << 2; + private static final int IS_DUMPING_STATIC_ARCHIVE = 1 << 3; + private static final int IS_LOGGING_LAMBDA_FORM_INVOKERS = 1 << 4; + private static final int IS_USING_ARCHIVE = 1 << 5; + private static final int configStatus = getCDSConfigStatus(); /** @@ -82,6 +84,10 @@ public class CDS { return (configStatus & IS_DUMPING_STATIC_ARCHIVE) != 0; } + public static boolean isDumpingAOTLinkedClasses() { + return (configStatus & IS_DUMPING_AOT_LINKED_CLASSES) != 0; + } + public static boolean isSingleThreadVM() { return isDumpingStaticArchive(); } diff --git a/test/hotspot/jtreg/ProblemList-AotJdk.txt b/test/hotspot/jtreg/ProblemList-AotJdk.txt index 047fc6d33f8..d292c23f690 100644 --- a/test/hotspot/jtreg/ProblemList-AotJdk.txt +++ b/test/hotspot/jtreg/ProblemList-AotJdk.txt @@ -17,3 +17,7 @@ compiler/intrinsics/klass/TestIsPrimitive.java 0000000 generic-all # It has the assumption about unresolved Integer. # However when AOTClassLinking is enabled, Integer is always resolved at JVM start-up. compiler/ciReplay/TestInliningProtectionDomain.java 0000000 generic-all + +# These tests fail often with AotJdk due to JDK-8323727 +compiler/arguments/TestStressReflectiveCode.java 8323727 generic-all +compiler/arraycopy/TestCloneWithStressReflectiveCode.java 8323727 generic-all diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/MethodHandleTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/MethodHandleTest.java index 972dc287af5..1d1984b5e4c 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/MethodHandleTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotClassLinking/MethodHandleTest.java @@ -33,6 +33,7 @@ * MethodHandleTestApp MethodHandleTestApp$A MethodHandleTestApp$B * UnsupportedBSMs UnsupportedBSMs$MyEnum * ObjectMethodsTest ObjectMethodsTest$C + * InterfaceWithEnum EnumWithClinit * @run driver MethodHandleTest AOT --two-step-training */ @@ -101,6 +102,11 @@ public class MethodHandleTest { out.shouldNotContain("MethodHandleTestApp."); out.shouldContain("intElm = 777"); } + + // For MethodHandleTestApp.testLambdaWithEnums() + if (runMode == RunMode.ASSEMBLY) { + out.shouldNotContain("EnumWithClinit."); + } } } } @@ -206,6 +212,8 @@ class MethodHandleTestApp { ObjectMethodsTest.testEqualsC(ObjectMethodsTest_handle); + testLambdaWithEnums(); + UnsupportedBSMs.invokeUnsupportedBSMs(); } @@ -275,6 +283,29 @@ class MethodHandleTestApp { } } } + + + static boolean InterfaceWithEnum_inited = false; + + // Enum types used in lambdas shouldn't be initialized during the assembly phase. + static void testLambdaWithEnums() { + if (InterfaceWithEnum_inited) { + throw new RuntimeException("InterfaceWithEnum should not be inited"); + } + + InterfaceWithEnum iwe = (x) -> { + System.out.println("Hello from testLambdaWithEnums"); + }; + + System.out.println(iwe); + if (InterfaceWithEnum_inited) { + throw new RuntimeException("InterfaceWithEnum should not be inited"); + } + iwe.func(EnumWithClinit.Dummy); + if (!InterfaceWithEnum_inited) { + throw new RuntimeException("InterfaceWithEnum should be inited"); + } + } } // Excerpt from test/jdk/java/lang/runtime/ObjectMethodsTest.java @@ -332,6 +363,18 @@ class ObjectMethodsTest { } } +interface InterfaceWithEnum { + void func(EnumWithClinit e); +} + +enum EnumWithClinit { + Dummy; + static { + MethodHandleTestApp.InterfaceWithEnum_inited = true; + System.out.println("EnumWithClinit."); + } +} + class UnsupportedBSMs { // This method is executed during the assembly phase. //