diff --git a/src/hotspot/share/cds/aotArtifactFinder.cpp b/src/hotspot/share/cds/aotArtifactFinder.cpp index f85f1e46520..4633da34e9c 100644 --- a/src/hotspot/share/cds/aotArtifactFinder.cpp +++ b/src/hotspot/share/cds/aotArtifactFinder.cpp @@ -253,7 +253,7 @@ void AOTArtifactFinder::add_cached_instance_class(InstanceKlass* ik) { add_cached_instance_class(nest_host); } - if (CDSConfig::is_dumping_final_static_archive() && ik->defined_by_other_loaders()) { + if (CDSConfig::is_dumping_final_static_archive() && ik->defined_by_other_loaders() && !ik->is_defined_by_aot_safe_custom_loader() ) { // The following are not appliable to unregistered classes return; } diff --git a/src/hotspot/share/cds/aotClassLinker.cpp b/src/hotspot/share/cds/aotClassLinker.cpp index 0ed17dd6746..db594400420 100644 --- a/src/hotspot/share/cds/aotClassLinker.cpp +++ b/src/hotspot/share/cds/aotClassLinker.cpp @@ -28,6 +28,7 @@ #include "cds/archiveBuilder.hpp" #include "cds/archiveUtils.inline.hpp" #include "cds/cdsConfig.hpp" +#include "cds/customLoaderSupport.hpp" #include "cds/heapShared.hpp" #include "cds/lambdaFormInvokers.inline.hpp" #include "classfile/classLoader.hpp" @@ -45,6 +46,12 @@ AOTClassLinker::ClassesTable* AOTClassLinker::_vm_classes = nullptr; AOTClassLinker::ClassesTable* AOTClassLinker::_candidates = nullptr; GrowableArrayCHeap* AOTClassLinker::_sorted_candidates = nullptr; +static const unsigned INITIAL_TABLE_SIZE = 997; // prime number +static const unsigned MAX_TABLE_SIZE = 10000; + +ClassLoaderIdToClassTableMap * _custom_loader_prelinked_table; +ArchivedCustomLoaderClassTableMap _archived_custom_loader_prelinked_classes_map; + #ifdef ASSERT bool AOTClassLinker::is_initialized() { assert(CDSConfig::is_dumping_archive(), "AOTClassLinker is for CDS dumping only"); @@ -59,6 +66,8 @@ void AOTClassLinker::initialize() { _candidates = new (mtClass)ClassesTable(); _sorted_candidates = new GrowableArrayCHeap(1000); + _custom_loader_prelinked_table = new (mtClass) ClassLoaderIdToClassTableMap(INITIAL_TABLE_SIZE, MAX_TABLE_SIZE); + for (auto id : EnumRange{}) { add_vm_class(vmClasses::klass_at(id)); } @@ -113,6 +122,11 @@ void AOTClassLinker::add_new_candidate(InstanceKlass* ik) { _candidates->put_when_absent(ik, true); _sorted_candidates->append(ik); + Symbol* loader_id; + loader_id = ik->cl_aot_identity(); + if (loader_id != nullptr) { + _custom_loader_prelinked_table->add_class(loader_id, ik); + } if (log_is_enabled(Info, aot, link)) { ResourceMark rm; log_info(aot, link)("%s %s %p", class_category_name(ik), ik->external_name(), ik); @@ -127,7 +141,7 @@ bool AOTClassLinker::try_add_candidate(InstanceKlass* ik) { assert(is_initialized(), "sanity"); assert(CDSConfig::is_dumping_aot_linked_classes(), "sanity"); - if (!SystemDictionaryShared::is_builtin(ik)) { + if (!SystemDictionaryShared::is_builtin(ik) && !ik->is_defined_by_aot_safe_custom_loader()) { // not loaded by a class loader which we know about return false; } @@ -190,6 +204,20 @@ void AOTClassLinker::add_candidates() { } } +ArchivedCustomLoaderClassTable* AOTClassLinker::get_archived_prelinked_table(Symbol* aot_id) { + return _archived_custom_loader_prelinked_classes_map.get_class_list(aot_id); +} + +void AOTClassLinker::all_symbols_do(MetaspaceClosure* it) { + _custom_loader_prelinked_table->iterate_all([&](Symbol*& loader_id, ClassList*& class_list) { + it->push(&loader_id); + }); +} + +void AOTClassLinker::serialize_prelinked_classes_map_header(SerializeClosure* soc) { + _archived_custom_loader_prelinked_classes_map.serialize_header(soc); +} + void AOTClassLinker::write_to_archive() { assert(is_initialized(), "sanity"); assert_at_safepoint(); @@ -200,6 +228,19 @@ void AOTClassLinker::write_to_archive() { 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)); + + _custom_loader_prelinked_table->write_to_archive(&_archived_custom_loader_prelinked_classes_map, "archived prelinked table"); + + if (log_is_enabled(Info, aot, link)) { + ResourceMark rm; + _custom_loader_prelinked_table->iterate_all([&](Symbol* loader_id, ClassList* class_list) { + log_info(aot, link)("Class loader \"%s\" has %d classes in prelinked table", loader_id->as_C_string(), class_list->length()); + for (int i = 0; i < class_list->length(); i++) { + InstanceKlass* ik = class_list->at(i); + log_info(aot, link)(" %s", ik->external_name()); + } + }); + } } } @@ -281,6 +322,8 @@ const char* AOTClassLinker::class_category_name(Klass* k) { return "plat"; } else if (loader == SystemDictionary::java_system_loader()) { return "app"; + } else if (k->cl_aot_identity() != nullptr) { + return "aotsafe_custom_lodaer"; } else { return "unreg"; } diff --git a/src/hotspot/share/cds/aotClassLinker.hpp b/src/hotspot/share/cds/aotClassLinker.hpp index 66270bdb9cf..0bd9b5a35dc 100644 --- a/src/hotspot/share/cds/aotClassLinker.hpp +++ b/src/hotspot/share/cds/aotClassLinker.hpp @@ -35,6 +35,7 @@ #include "utilities/macros.hpp" class AOTLinkedClassTable; +class ArchivedCustomLoaderClassTable; class InstanceKlass; class SerializeClosure; template class Array; @@ -111,6 +112,10 @@ class AOTClassLinker : AllStatic { static int num_app_initiated_classes(); static int num_platform_initiated_classes(); + static void all_symbols_do(MetaspaceClosure* it); + static void serialize_prelinked_classes_map_header(SerializeClosure* soc); + static ArchivedCustomLoaderClassTable* get_archived_prelinked_table(Symbol* aot_id); + // Used in logging: "boot1", "boot2", "plat", "app" and "unreg"; static const char* class_category_name(AOTLinkedClassCategory category); static const char* class_category_name(Klass* k); diff --git a/src/hotspot/share/cds/aotClassLocation.cpp b/src/hotspot/share/cds/aotClassLocation.cpp index d141ad49113..687e9f0ebfb 100644 --- a/src/hotspot/share/cds/aotClassLocation.cpp +++ b/src/hotspot/share/cds/aotClassLocation.cpp @@ -26,6 +26,7 @@ #include "cds/aotLogging.hpp" #include "cds/aotMetaspace.hpp" #include "cds/archiveBuilder.hpp" +#include "cds/archiveUtils.inline.hpp" #include "cds/cdsConfig.hpp" #include "cds/dynamicArchive.hpp" #include "cds/filemap.hpp" @@ -33,6 +34,8 @@ #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.hpp" #include "classfile/javaClasses.hpp" +#include "classfile/compactHashtable.hpp" +#include "classfile/symbolTable.hpp" #include "logging/log.hpp" #include "logging/logStream.hpp" #include "memory/metadataFactory.hpp" @@ -41,8 +44,10 @@ #include "oops/array.hpp" #include "oops/objArrayKlass.hpp" #include "runtime/arguments.hpp" +#include "runtime/mutexLocker.hpp" #include "utilities/classpathStream.hpp" #include "utilities/formatBuffer.hpp" +#include "utilities/resizableHashTable.hpp" #include "utilities/stringUtils.hpp" #include @@ -156,6 +161,13 @@ class AllClassLocationStreams { } }; +class URLClassLoaderClassLocationStream : public ClassLocationStream { +public: + URLClassLoaderClassLocationStream(const char* classpath) : ClassLocationStream() { + add_paths_in_classpath(classpath); + } +}; + static bool has_jar_suffix(const char* filename) { // In jdk.internal.module.ModulePath.readModule(), it checks for the ".jar" suffix. // Performing the same check here. @@ -1095,3 +1107,134 @@ void AOTClassLocationConfig::print_on(outputStream* st) const { st->print_cr("(%-6s) [%d] = %s", type, i, path); } } + +typedef ResizeableHashTable AOTIdToURLLoaderClasspath; +static AOTIdToURLLoaderClasspath* _aot_id_to_classpath = nullptr; + +static inline bool ucc_symbol_equals(URLClassLoaderClasspath* ucc, Symbol* loader_id, int len_unused) { + return ucc->loader_id()->equals(loader_id); +} + +class ArchivedAOTIdToClasspathMap : public OffsetCompactHashtable {}; +static ArchivedAOTIdToClasspathMap _archived_aot_id_to_classpath; + +void URLClassLoaderClasspathSupport::init() { + _aot_id_to_classpath = new (mtClass) AOTIdToURLLoaderClasspath(11, 1000); + if (CDSConfig::is_dumping_final_static_archive()) { + reload_runtime_map(); + } +} + +void URLClassLoaderClasspathSupport::reload_runtime_map() { + _archived_aot_id_to_classpath.iterate([&](URLClassLoaderClasspath* ucc) { + GrowableClassLocationArray* locations = new GrowableClassLocationArray(ucc->num_entries()); + for (int i = 0; i < ucc->num_entries(); i++) { + locations->append(ucc->class_location_at(i)); + log_info(class, path)("path [%d] = %s", i, ucc->class_location_at(i)->path()); + } + _aot_id_to_classpath->put(ucc->loader_id(), locations); + return true; + }); +} + +bool URLClassLoaderClasspathSupport::add_urlclassloader_classpath(ClassLoaderData* loader_data, Symbol* aot_id, const char* classpath) { + assert(_aot_id_to_classpath != nullptr, "sanity check"); + if (_aot_id_to_classpath->contains(aot_id)) { + // cannot allow aot_id clash; return without doing anything + return false; + } + GrowableClassLocationArray* locations = new GrowableClassLocationArray(10); + URLClassLoaderClassLocationStream css(classpath); + for (css.start(); css.has_next(); ) { + const char* path = css.get_next(); + AOTClassLocation* cs = AOTClassLocation::allocate(JavaThread::current(), path, locations->length(), AOTClassLocation::Group::URLCLASSLOADER_CLASSPATH, false); + log_info(class, path)("path [%d] = %s", locations->length(), path); + locations->append(cs); + } + _aot_id_to_classpath->put(aot_id, locations); + return true; +} + +class URLClassLoaderClasspathArchiver : StackObj { +private: + CompactHashtableWriter* _writer; + ArchiveBuilder* _builder; +public: + URLClassLoaderClasspathArchiver(CompactHashtableWriter* writer) : _writer(writer), + _builder(ArchiveBuilder::current()) + {} + + bool do_entry(Symbol* loader_id, GrowableClassLocationArray* class_locations) { + // If the loader_id has not been archived yet, it implies no class was loaded using this loader. + // So there is no point to archive classpath for this loader_id. + if (!_builder->has_been_archived(loader_id)) { + return true; + } + Array* archived_copy = ArchiveBuilder::new_ro_array(class_locations->length()); + for (int i = 0; i < class_locations->length(); i++) { + archived_copy->at_put(i, class_locations->at(i)->write_to_archive()); + ArchivePtrMarker::mark_pointer((address*)archived_copy->adr_at(i)); + } + URLClassLoaderClasspath* ucc = (URLClassLoaderClasspath*)ArchiveBuilder::ro_region_alloc(sizeof(URLClassLoaderClasspath)); + assert(_builder->has_been_archived(loader_id), "must be"); + Symbol* buffered_sym = _builder->get_buffered_addr(loader_id); + ucc->init(buffered_sym, archived_copy); + ArchivePtrMarker::mark_pointer(ucc->loader_id_addr()); + ArchivePtrMarker::mark_pointer(ucc->class_locations_addr()); + unsigned int hash = Symbol::symbol_hash(loader_id); + _writer->add(hash, AOTCompressedPointers::encode_not_null((address)ucc)); + return true; + } +}; + +void URLClassLoaderClasspathSupport::archive_classpath_map() { + CompactHashtableStats stats; + CompactHashtableWriter writer(_aot_id_to_classpath->number_of_entries(), &stats); + URLClassLoaderClasspathArchiver archiver(&writer); + _aot_id_to_classpath->iterate(&archiver); + writer.dump(&_archived_aot_id_to_classpath, "archived prelinked table"); +} + +void URLClassLoaderClasspathSupport::serialize_classpath_map_table_header(SerializeClosure* soc) { + _archived_aot_id_to_classpath.serialize_header(soc); +} + +bool URLClassLoaderClasspathSupport::claim_and_verify_archived_classpath(ClassLoaderData* loader_data, Symbol* aot_id, const char* classpath) { + ResourceMark rm; + assert(aot_id != nullptr, "sanity check"); + const char* aot_id_str = aot_id->as_C_string(); + unsigned int hash = Symbol::symbol_hash(aot_id); + URLClassLoaderClasspath* archived_classpath = _archived_aot_id_to_classpath.lookup(aot_id, hash, /*len*/0); // len is ignored + if (archived_classpath == nullptr) { + aot_log_trace(aot)("No archived entry found for URLClassLoader (id=%s)", aot_id_str); + return false; + } + + { + // Acquire lock before loader_data claims the archived_classpath + MutexLocker mu(URLClassLoaderClasspath_lock, Mutex::_no_safepoint_check_flag); + if (archived_classpath->cld_owner() != nullptr) { + aot_log_warning(aot)("Duplicate URLClassLoader with same classpath found"); + return false; + } + archived_classpath->set_cld_owner(loader_data); + } + + URLClassLoaderClassLocationStream uccs(classpath); + uccs.start(); + for (int i = 0; i < archived_classpath->num_entries(); i++) { + AOTClassLocation* location = archived_classpath->class_location_at(i); + const char* archived_path = location->path(); + if (!uccs.has_next()) { + aot_log_warning(aot)("URLClassLoader (id=%s) classpath validation failed (reason: classpath has fewer elements than expected)", aot_id_str); + return false; + } + const char* runtime_path = uccs.get_next(); + if (!location->check(runtime_path, true)) { + aot_log_warning(aot)("URLClassLoader (id=%s) classpath validation failed", aot_id_str); + return false; + } + } + return true; +} diff --git a/src/hotspot/share/cds/aotClassLocation.hpp b/src/hotspot/share/cds/aotClassLocation.hpp index 89a5e6bc939..cef414715b4 100644 --- a/src/hotspot/share/cds/aotClassLocation.hpp +++ b/src/hotspot/share/cds/aotClassLocation.hpp @@ -60,7 +60,8 @@ class AOTClassLocation { MODULES_IMAGE, BOOT_CLASSPATH, APP_CLASSPATH, - MODULE_PATH + MODULE_PATH, + URLCLASSLOADER_CLASSPATH }; private: enum class FileType : int { @@ -88,6 +89,7 @@ class AOTClassLocation { static AOTClassLocation* allocate(JavaThread* current, const char* path, int index, Group group, bool from_cpattr = false, bool is_jrt = false); + size_t path_length() const { return _path_length; } size_t total_size() const { return manifest_offset() + _manifest_length + 1; } const char* path() const { return ((const char*)this) + path_offset(); } size_t manifest_length() const { return _manifest_length; } @@ -115,6 +117,29 @@ class AOTClassLocation { bool check(const char* runtime_path, bool has_aot_linked_classes) const; }; +using GrowableClassLocationArray = GrowableArrayCHeap; + +class URLClassLoaderClasspath { + private: + Symbol* _loader_id; + Array* _class_locations; + ClassLoaderData* _cld_owner; + public: + void init(Symbol* aot_id, Array* class_locations) { + _loader_id = aot_id; + _class_locations = class_locations; + _cld_owner = nullptr; + } + Symbol* loader_id() const { return _loader_id; } + address* loader_id_addr() const { return (address*)&_loader_id; } + Array* class_locations() const { return _class_locations; } + address* class_locations_addr() const { return (address*)&_class_locations; } + AOTClassLocation* class_location_at(int i) { return _class_locations->at(i); } + int num_entries() { return _class_locations->length(); } + ClassLoaderData* cld_owner() const { return _cld_owner; } + void set_cld_owner(ClassLoaderData* cld) { _cld_owner = cld; } +}; + // AOTClassLocationConfig // // Keep track of the set of AOTClassLocations used when an AOTCache is created. @@ -134,7 +159,6 @@ class AOTClassLocation { class AOTClassLocationConfig : public CHeapObj { using Group = AOTClassLocation::Group; - using GrowableClassLocationArray = GrowableArrayCHeap; // Note: both of the following are non-null if we are dumping a dynamic archive. static AOTClassLocationConfig* _dumptime_instance; @@ -277,5 +301,14 @@ class AOTClassLocationConfig : public CHeapObj { static void print(); }; +class URLClassLoaderClasspathSupport : AllStatic { +public: + static void init(); + static void reload_runtime_map(); + static bool add_urlclassloader_classpath(ClassLoaderData* loader_data, Symbol* aot_id, const char* classpath); + static void archive_classpath_map(); + static void serialize_classpath_map_table_header(SerializeClosure* soc); + static bool claim_and_verify_archived_classpath(ClassLoaderData* loader_data, Symbol* aot_id, const char* classpath); +}; #endif // SHARE_CDS_AOTCLASSLOCATION_HPP diff --git a/src/hotspot/share/cds/aotConstantPoolResolver.cpp b/src/hotspot/share/cds/aotConstantPoolResolver.cpp index 19d4bb71b36..8a48df67983 100644 --- a/src/hotspot/share/cds/aotConstantPoolResolver.cpp +++ b/src/hotspot/share/cds/aotConstantPoolResolver.cpp @@ -32,6 +32,7 @@ #include "cds/finalImageRecipes.hpp" #include "cds/heapShared.hpp" #include "cds/lambdaFormInvokers.inline.hpp" +#include "cds/unregisteredClasses.hpp" #include "classfile/classLoader.hpp" #include "classfile/dictionary.hpp" #include "classfile/symbolTable.hpp" @@ -193,7 +194,9 @@ Klass* AOTConstantPoolResolver::find_loaded_class(Thread* current, oop class_loa if (k != nullptr) { return k; } - if (h_loader() == SystemDictionary::java_system_loader()) { + if (h_loader() == UnregisteredClasses::unregistered_class_loader(current)()) { + return find_loaded_class(current, SystemDictionary::java_system_loader(), name); + } else if (h_loader() == SystemDictionary::java_system_loader()) { return find_loaded_class(current, SystemDictionary::java_platform_loader(), name); } else if (h_loader() == SystemDictionary::java_platform_loader()) { return find_loaded_class(current, nullptr, name); @@ -222,7 +225,7 @@ void AOTConstantPoolResolver::resolve_string(constantPoolHandle cp, int cp_index #endif void AOTConstantPoolResolver::preresolve_class_cp_entries(JavaThread* current, InstanceKlass* ik, GrowableArray* preresolve_list) { - if (!SystemDictionaryShared::is_builtin_loader(ik->class_loader_data())) { + if (!SystemDictionaryShared::is_builtin_loader(ik->class_loader_data()) && ik->cl_aot_identity() == nullptr) { return; } diff --git a/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp index 5375fa29f99..69a12dca36e 100644 --- a/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp +++ b/src/hotspot/share/cds/aotLinkedClassBulkLoader.cpp @@ -28,9 +28,11 @@ #include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/aotLinkedClassTable.hpp" #include "cds/cdsConfig.hpp" +#include "cds/customLoaderSupport.hpp" #include "cds/heapShared.hpp" #include "classfile/classLoaderData.hpp" #include "classfile/classLoaderDataShared.hpp" +#include "classfile/dictionary.hpp" #include "classfile/javaClasses.hpp" #include "classfile/systemDictionary.hpp" #include "classfile/systemDictionaryShared.hpp" @@ -46,6 +48,7 @@ #include "runtime/perfData.inline.hpp" #include "runtime/serviceThread.hpp" #include "utilities/growableArray.hpp" +#include "utilities/resizableHashTable.hpp" static PerfCounter* _perf_classes_preloaded = nullptr; static PerfTickCounters* _perf_class_preload_counters = nullptr; @@ -97,6 +100,86 @@ void AOTLinkedClassBulkLoader::preload_classes_impl(TRAPS) { preload_classes_in_table(table->app(), "app", h_system_loader, CHECK); } +void AOTLinkedClassBulkLoader::preload_classes_for_loader(ClassLoaderData* loader_data, TRAPS) { + Handle h_loader(THREAD, loader_data->class_loader_handle().resolve()); + preload_classes_for_loader_impl(h_loader, CHECK); +} + +void AOTLinkedClassBulkLoader::preload_classes_for_loader_impl(Handle loader_obj, TRAPS) { + assert(!SystemDictionary::is_builtin_class_loader(loader_obj()), "must not be called for builtin loaders"); + Symbol* aot_id = java_lang_ClassLoader::loader_data(loader_obj())->aot_identity(); + if (aot_id == nullptr) { + return; + } + ArchivedCustomLoaderClassTable* table = AOTClassLinker::get_archived_prelinked_table(aot_id); + preload_classes_in_table(table->class_list(), aot_id->as_C_string(), loader_obj, CHECK); + MonitorLocker mu1(SystemDictionary_lock); + mark_initiating_loader(THREAD, loader_obj()); +} + +class DictionaryCopier : public KlassClosure { + private: + Dictionary* _dict; + GrowableArray _klasses; + public: + DictionaryCopier(Dictionary* dict) : _dict(dict) {} + void do_klass(Klass* k) { + assert(k->is_instance_klass(), "must be"); + Symbol* name = k->name(); + _klasses.append(InstanceKlass::cast(k)); + } + GrowableArray* klasses() { + return &_klasses; + } +}; + +void AOTLinkedClassBulkLoader::mark_initiating_loader(JavaThread* currentThread, oop loader) { + assert(!SystemDictionary::is_builtin_class_loader(loader), "does not work for built-in loaders"); + ClassLoaderData* cl_data = java_lang_ClassLoader::loader_data(loader); + oop parent = java_lang_ClassLoader::parent(loader); + assert(parent != nullptr, "custom loader's parent loader cannot be null"); + ClassLoaderData* parent_cl_data = java_lang_ClassLoader::loader_data(parent); + Dictionary* parent_dict = parent_cl_data->dictionary(); + Dictionary* loader_dict = cl_data->dictionary(); + DictionaryCopier copier(loader_dict); + parent_dict->all_entries_do(&copier); + GrowableArray* klasses = copier.klasses(); + for (int i = 0; i < klasses->length(); i++) { + Symbol* name = klasses->at(i)->name(); + if (loader_dict->find_class(currentThread, name) == nullptr) { + loader_dict->add_klass(currentThread, name, klasses->at(i)); + } + } +} + +void AOTLinkedClassBulkLoader::mark_initiating_loader(JavaThread* currentThread, oop loader, ResizeableHashTable& processed) { + if (SystemDictionary::is_builtin_class_loader(loader)) { + //terminating condition: loader is builtin loader for which this computation has already been done + return; + } + ClassLoaderData* cl_data = java_lang_ClassLoader::loader_data(loader); + if (processed.contains(cl_data)) { + return; + } + oop parent = java_lang_ClassLoader::parent(loader); + assert(parent != nullptr, "custom loader's parent loader cannot be null"); + mark_initiating_loader(currentThread, parent, processed); + ClassLoaderData* parent_cl_data = java_lang_ClassLoader::loader_data(parent); + Dictionary* parent_dict = parent_cl_data->dictionary(); + Dictionary* loader_dict = cl_data->dictionary(); + DictionaryCopier copier(loader_dict); + parent_dict->all_entries_do(&copier); + GrowableArray* klasses = copier.klasses(); + for (int i = 0; i < klasses->length(); i++) { + Symbol* name = klasses->at(i)->name(); + if (loader_dict->find_class(currentThread, name) == nullptr) { + loader_dict->add_klass(currentThread, name, klasses->at(i)); + } + } + bool created; + processed.put_if_absent(cl_data, &created); +} + void AOTLinkedClassBulkLoader::preload_classes_in_table(Array* classes, const char* category_name, Handle loader, TRAPS) { if (classes == nullptr) { @@ -167,6 +250,23 @@ void AOTLinkedClassBulkLoader::link_classes_impl(TRAPS) { log_info(aot, init)("------ finished early class init"); } +void AOTLinkedClassBulkLoader::link_classes_for_loader(ClassLoaderData* loader_data, TRAPS) { + Handle h_loader(THREAD, loader_data->class_loader_handle().resolve()); + link_classes_for_loader_impl(h_loader, CHECK); +} + +void AOTLinkedClassBulkLoader::link_classes_for_loader_impl(Handle loader_obj, TRAPS) { + precond(CDSConfig::is_using_aot_linked_classes()); + assert(!SystemDictionary::is_builtin_class_loader(loader_obj()), "must not be called for builtin loaders"); + + Symbol* aot_id = java_lang_ClassLoader::loader_data(loader_obj())->aot_identity(); + if (aot_id == nullptr) { + return; + } + ArchivedCustomLoaderClassTable* table = AOTClassLinker::get_archived_prelinked_table(aot_id); + link_classes_in_table(table->class_list(), CHECK); +} + void AOTLinkedClassBulkLoader::link_classes_in_table(Array* classes, TRAPS) { if (classes != nullptr) { for (int i = 0; i < classes->length(); i++) { diff --git a/src/hotspot/share/cds/aotLinkedClassBulkLoader.hpp b/src/hotspot/share/cds/aotLinkedClassBulkLoader.hpp index d18fff96dfd..59cca0e2a8d 100644 --- a/src/hotspot/share/cds/aotLinkedClassBulkLoader.hpp +++ b/src/hotspot/share/cds/aotLinkedClassBulkLoader.hpp @@ -76,6 +76,13 @@ class AOTLinkedClassBulkLoader : AllStatic { static bool is_initializing_classes_early(); static void replay_training_at_init_for_preloaded_classes(TRAPS) NOT_CDS_RETURN; static void print_counters_on(outputStream* st) NOT_CDS_RETURN; + static void mark_initiating_loader(JavaThread* currentThread, oop loader, ResizeableHashTable& processed); + + static void preload_classes_for_loader(ClassLoaderData* loader_data, TRAPS); + static void preload_classes_for_loader_impl(Handle loader_obj, TRAPS); + static void mark_initiating_loader(JavaThread* currentThread, oop loader); + static void link_classes_for_loader(ClassLoaderData* loader_data, TRAPS); + static void link_classes_for_loader_impl(Handle loader_obj, TRAPS); }; #endif // SHARE_CDS_AOTLINKEDCLASSBULKLOADER_HPP diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index d1422ac2616..61b0404eda3 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -532,6 +532,8 @@ void AOTMetaspace::serialize(SerializeClosure* soc) { HeapShared::serialize_tables(soc); SystemDictionaryShared::serialize_dictionary_headers(soc); AOTLinkedClassBulkLoader::serialize(soc); + AOTClassLinker::serialize_prelinked_classes_map_header(soc); + URLClassLoaderClasspathSupport::serialize_classpath_map_table_header(soc); FinalImageRecipes::serialize(soc); TrainingData::serialize(soc); InstanceMirrorKlass::serialize_offsets(soc); @@ -721,6 +723,7 @@ class StaticArchiveBuilder : public ArchiveBuilder { for (int i = 0; i < _pending_method_handle_intrinsics->length(); i++) { it->push(_pending_method_handle_intrinsics->adr_at(i)); } + AOTClassLinker::all_symbols_do(it); } }; @@ -742,6 +745,7 @@ char* VM_PopulateDumpSharedSpace::dump_read_only_tables(AOTClassLocationConfig*& SystemDictionaryShared::write_to_archive(); cl_config = AOTClassLocationConfig::dumptime()->write_to_archive(); AOTClassLinker::write_to_archive(); + URLClassLoaderClasspathSupport::archive_classpath_map(); if (CDSConfig::is_dumping_preimage_static_archive()) { FinalImageRecipes::record_recipes(); } diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp index 00f350c15aa..ce687bc4461 100644 --- a/src/hotspot/share/cds/archiveBuilder.cpp +++ b/src/hotspot/share/cds/archiveBuilder.cpp @@ -322,6 +322,44 @@ int ArchiveBuilder::compare_klass_by_name(Klass** a, Klass** b) { void ArchiveBuilder::sort_klasses() { aot_log_info(aot)("Sorting classes ... "); _klasses->sort(compare_klass_by_name); + GrowableArray* unused = _klasses; + _klasses = sort_klasses_by_hierarchy(); + delete unused; +} + +static const int INITIAL_TABLE_SIZE = 15889; +using ClassesTable = HashTable; +ClassesTable* _classes_added = nullptr; + +static void add_to_sorted_list(GrowableArray* sorted_list, Klass* k) { + if (k == nullptr) { + return; + } + if (_classes_added->get(k) != nullptr) { + return; + } + add_to_sorted_list(sorted_list, k->super()); + + if (k->is_instance_klass()) { + InstanceKlass* ik = (InstanceKlass*)k; + Array* interfaces = ik->local_interfaces(); + int num_interfaces = interfaces->length(); + for (int index = 0; index < num_interfaces; index++) { + InstanceKlass* intf = interfaces->at(index); + add_to_sorted_list(sorted_list, intf); + } + } + _classes_added->put_when_absent(k, true); + sorted_list->append(k); +} + +GrowableArray* ArchiveBuilder::sort_klasses_by_hierarchy() { + GrowableArray* sorted_by_hierarchy = new (mtClassShared) GrowableArray(_klasses->length(), mtClassShared); + _classes_added = new (mtClass)ClassesTable(); + for (int i = 0; i < _klasses->length(); i++) { + add_to_sorted_list(sorted_by_hierarchy, _klasses->at(i)); + } + return sorted_by_hierarchy; } address ArchiveBuilder::reserve_buffer() { diff --git a/src/hotspot/share/cds/archiveBuilder.hpp b/src/hotspot/share/cds/archiveBuilder.hpp index 68394a24341..2f79de89536 100644 --- a/src/hotspot/share/cds/archiveBuilder.hpp +++ b/src/hotspot/share/cds/archiveBuilder.hpp @@ -279,6 +279,7 @@ class ArchiveBuilder : public StackObj { void iterate_sorted_roots(MetaspaceClosure* it); void sort_klasses(); + GrowableArray* sort_klasses_by_hierarchy(); static int compare_symbols_by_address(Symbol** a, Symbol** b); static int compare_klass_by_name(Klass** a, Klass** b); void update_hidden_class_loader_type(InstanceKlass* ik) NOT_CDS_JAVA_HEAP_RETURN; diff --git a/src/hotspot/share/cds/archiveUtils.hpp b/src/hotspot/share/cds/archiveUtils.hpp index 7e3070a9809..77f062a169a 100644 --- a/src/hotspot/share/cds/archiveUtils.hpp +++ b/src/hotspot/share/cds/archiveUtils.hpp @@ -261,20 +261,20 @@ class ReadClosure : public SerializeClosure { }; class ArchiveUtils { - template static Array* archive_non_ptr_array(GrowableArray* tmp_array); - template static Array* archive_ptr_array(GrowableArray* tmp_array); + template static Array* archive_non_ptr_array(GrowableArrayView* tmp_array); + template static Array* archive_ptr_array(GrowableArrayView* tmp_array); public: static void log_to_classlist(BootstrapInfo* bootstrap_specifier, TRAPS) NOT_CDS_RETURN; static bool has_aot_initialized_mirror(InstanceKlass* src_ik); template ::value)> - static Array* archive_array(GrowableArray* tmp_array) { + static Array* archive_array(GrowableArrayView* tmp_array) { return archive_non_ptr_array(tmp_array); } template ::value)> - static Array* archive_array(GrowableArray* tmp_array) { + static Array* archive_array(GrowableArrayView* tmp_array) { return archive_ptr_array(tmp_array); } diff --git a/src/hotspot/share/cds/archiveUtils.inline.hpp b/src/hotspot/share/cds/archiveUtils.inline.hpp index 6f635d01745..75e998fccf0 100644 --- a/src/hotspot/share/cds/archiveUtils.inline.hpp +++ b/src/hotspot/share/cds/archiveUtils.inline.hpp @@ -55,7 +55,7 @@ inline bool SharedDataRelocator::do_bit(size_t offset) { // Returns the address of an Array that's allocated in the ArchiveBuilder "buffer" space. template -Array* ArchiveUtils::archive_non_ptr_array(GrowableArray* tmp_array) { +Array* ArchiveUtils::archive_non_ptr_array(GrowableArrayView* tmp_array) { ArchiveBuilder* builder = ArchiveBuilder::current(); Array* archived_array = ArchiveBuilder::new_ro_array(tmp_array->length()); @@ -72,7 +72,7 @@ Array* ArchiveUtils::archive_non_ptr_array(GrowableArray* tmp_array) { // - a source object that has been archived; or // - (only when dumping dynamic archive) an object in the static archive. template -Array* ArchiveUtils::archive_ptr_array(GrowableArray* tmp_array) { +Array* ArchiveUtils::archive_ptr_array(GrowableArrayView* tmp_array) { ArchiveBuilder* builder = ArchiveBuilder::current(); const bool is_dynamic_dump = CDSConfig::is_dumping_dynamic_archive(); diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 0d041562127..5e36d27c362 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -61,6 +61,7 @@ bool CDSConfig::_old_cds_flags_used = false; bool CDSConfig::_new_aot_flags_used = false; bool CDSConfig::_disable_heap_dumping = false; bool CDSConfig::_is_at_aot_safepoint = false; +bool CDSConfig::_supports_custom_loaders = false; const char* CDSConfig::_default_archive_path = nullptr; const char* CDSConfig::_input_static_archive_path = nullptr; @@ -764,6 +765,10 @@ bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_fla } } + if (is_dumping_archive()) { + set_custom_loaders_support(AOTCacheSupportForCustomLoader); + } + return true; } @@ -1172,3 +1177,11 @@ void CDSConfig::enable_dumping_aot_code() { bool CDSConfig::is_dumping_adapters() { return (AOTAdapterCaching && is_dumping_final_static_archive()); } + +bool CDSConfig::supports_custom_loaders() { + return _supports_custom_loaders; +} + +void CDSConfig::set_custom_loaders_support(bool value) { + _supports_custom_loaders = value; +} diff --git a/src/hotspot/share/cds/cdsConfig.hpp b/src/hotspot/share/cds/cdsConfig.hpp index b7845bdc033..3c2d0ea2a3d 100644 --- a/src/hotspot/share/cds/cdsConfig.hpp +++ b/src/hotspot/share/cds/cdsConfig.hpp @@ -55,6 +55,8 @@ class CDSConfig : public AllStatic { static bool _new_aot_flags_used; static bool _disable_heap_dumping; + static bool _supports_custom_loaders; + static JavaThread* _dumper_thread; #endif @@ -208,6 +210,9 @@ class CDSConfig : public AllStatic { static void stop_dumping_full_module_graph(const char* reason = nullptr) NOT_CDS_JAVA_HEAP_RETURN; static void stop_using_full_module_graph(const char* reason = nullptr) NOT_CDS_JAVA_HEAP_RETURN; + static bool supports_custom_loaders(); + static void set_custom_loaders_support(bool value); + // --- AOT code static bool is_dumping_aot_code() NOT_CDS_RETURN_(false); diff --git a/src/hotspot/share/cds/cdsProtectionDomain.cpp b/src/hotspot/share/cds/cdsProtectionDomain.cpp index ff15fdccabe..399c401ec2d 100644 --- a/src/hotspot/share/cds/cdsProtectionDomain.cpp +++ b/src/hotspot/share/cds/cdsProtectionDomain.cpp @@ -119,7 +119,7 @@ PackageEntry* CDSProtectionDomain::get_package_entry_from_class(InstanceKlass* i PackageEntry* pkg_entry = ik->package(); if (CDSConfig::is_using_full_module_graph() && ik->in_aot_cache() && pkg_entry != nullptr) { assert(AOTMetaspace::in_aot_cache(pkg_entry), "must be"); - assert(!ik->defined_by_other_loaders(), "unexpected archived package entry for an unregistered class"); + assert(!ik->defined_by_other_loaders() || ik->cl_aot_identity() != nullptr, "unexpected archived package entry for an unregistered class"); return pkg_entry; } TempNewSymbol pkg_name = ClassLoader::package_from_class_name(ik->name()); diff --git a/src/hotspot/share/cds/cds_globals.hpp b/src/hotspot/share/cds/cds_globals.hpp index 43c1f339de9..2cb0a374831 100644 --- a/src/hotspot/share/cds/cds_globals.hpp +++ b/src/hotspot/share/cds/cds_globals.hpp @@ -226,6 +226,10 @@ \ product(bool, SkipArchiveHeapVerification, false, \ "Skip verification of CDS archive heap") \ + \ + product(bool, AOTCacheSupportForCustomLoader, false, \ + "Enable support for custom loaders in AOTCache") \ + // end of CDS_FLAGS diff --git a/src/hotspot/share/cds/customLoaderSupport.hpp b/src/hotspot/share/cds/customLoaderSupport.hpp new file mode 100644 index 00000000000..aa04032634f --- /dev/null +++ b/src/hotspot/share/cds/customLoaderSupport.hpp @@ -0,0 +1,95 @@ + +#ifndef SHARE_CDS_CUSTOM_LOADER_SUPPORT_HPP +#define SHARE_CDS_CUSTOM_LOADER_SUPPORT_HPP + +#include "classfile/compactHashtable.hpp" +#include "oops/instanceKlass.hpp" +#include "utilities/growableArray.hpp" +#include "utilities/resizableHashTable.hpp" + +class ArchivedCustomLoaderClassTable { +private: + Symbol* _loader_id; + Array* _class_list; +public: + void init(Symbol* aot_id, Array* class_list) { + _loader_id = aot_id; + _class_list = class_list; + } + Symbol* loader_id() const { return _loader_id; } + address* loader_id_addr() const { return (address*)&_loader_id; } + Array* class_list() const { return _class_list; } + address* class_list_addr() const { return (address*)&_class_list; } + + void mark_pointers() { + ArchivePtrMarker::mark_pointer(loader_id_addr()); + ArchivePtrMarker::mark_pointer(class_list_addr()); + } +}; + +inline bool custom_loader_class_list_equals(ArchivedCustomLoaderClassTable* table, Symbol* loader_id, int len_unused) { + return table->loader_id()->equals(loader_id); +} + +class ArchivedCustomLoaderClassTableMap : public OffsetCompactHashtable +{ +public: + ArchivedCustomLoaderClassTable* get_class_list(Symbol* aot_id) { + unsigned int hash = Symbol::symbol_hash(aot_id); + return lookup(aot_id, hash, 0 /* ignored */); + } +}; + +typedef GrowableArrayCHeap ClassList; + +class ClassLoaderIdToClassTableMap : public ResizeableHashTable +{ + using ResizeableHashTableBase = ResizeableHashTable; +private: + class CopyClassTableToArchive : StackObj { + private: + CompactHashtableWriter* _writer; + ArchiveBuilder* _builder; + public: + CopyClassTableToArchive(CompactHashtableWriter* writer) : _writer(writer), + _builder(ArchiveBuilder::current()) + {} + + bool do_entry(Symbol* loader_id, ClassList* table) { + ArchivedCustomLoaderClassTable* tableForLoader = (ArchivedCustomLoaderClassTable*)ArchiveBuilder::ro_region_alloc(sizeof(ArchivedCustomLoaderClassTable)); + assert(_builder->has_been_archived(loader_id), "must be"); + Symbol* buffered_loader_id = _builder->get_buffered_addr(loader_id); + tableForLoader->init(buffered_loader_id, ArchiveUtils::archive_array(table)); + tableForLoader->mark_pointers(); + unsigned int hash = Symbol::symbol_hash(loader_id); + _writer->add(hash, AOTCompressedPointers::encode_not_null((address)tableForLoader)); + return true; + } + }; + +public: + ClassLoaderIdToClassTableMap(unsigned size, unsigned max_size) : ResizeableHashTableBase(size, max_size) {} + + void add_class(Symbol* loader_id, InstanceKlass* ik) { + assert(loader_id != nullptr, "sanity check"); + ClassList** class_list_ptr = get(loader_id); + ClassList* class_list = nullptr; + if (class_list_ptr != nullptr) { + class_list = *class_list_ptr; + } else { + class_list = new ClassList(1000); + put(loader_id, class_list); + } + class_list->append(ik); + } + + void write_to_archive(ArchivedCustomLoaderClassTableMap* archived_map, const char* map_name) { + CompactHashtableStats stats; + CompactHashtableWriter writer(number_of_entries(), &stats); + CopyClassTableToArchive archiver(&writer); + iterate(&archiver); + writer.dump(archived_map, map_name); + } +}; + +#endif // SHARE_CDS_CUSTOM_LOADER_SUPPORT_HPP diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index 4d8ed75c316..73e8bb03f94 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -248,6 +248,7 @@ void FileMapHeader::populate(FileMapInfo *info, size_t core_region_alignment, _use_optimized_module_handling = CDSConfig::is_using_optimized_module_handling(); _has_aot_linked_classes = CDSConfig::is_dumping_aot_linked_classes(); _has_full_module_graph = CDSConfig::is_dumping_full_module_graph(); + _supports_custom_loaders = CDSConfig::supports_custom_loaders(); _gc_kind = (int)Universe::heap()->kind(); jio_snprintf(_gc_name, sizeof(_gc_name), Universe::heap()->name()); @@ -333,6 +334,7 @@ void FileMapHeader::print(outputStream* st) { st->print_cr("- use_optimized_module_handling: %d", _use_optimized_module_handling); st->print_cr("- has_full_module_graph %d", _has_full_module_graph); st->print_cr("- has_aot_linked_classes %d", _has_aot_linked_classes); + st->print_cr("- supports_custom_loaders: %d", _supports_custom_loaders); st->print_cr("- ptrmap_size_in_bits: %zu", _ptrmap_size_in_bits); } @@ -1770,6 +1772,9 @@ bool FileMapInfo::open_as_input() { } bool FileMapInfo::validate_aot_class_linking() { + if (header()->supports_custom_loaders()) { + CDSConfig::set_custom_loaders_support(true); + } // These checks need to be done after FileMapInfo::initialize(), which gets called before Universe::heap() // is available. if (header()->has_aot_linked_classes()) { diff --git a/src/hotspot/share/cds/filemap.hpp b/src/hotspot/share/cds/filemap.hpp index 7174c76e635..97312377e64 100644 --- a/src/hotspot/share/cds/filemap.hpp +++ b/src/hotspot/share/cds/filemap.hpp @@ -144,6 +144,7 @@ class FileMapHeader: private CDSFileMapHeaderBase { // some expensive operations. bool _has_aot_linked_classes; // Was the CDS archive created with -XX:+AOTClassLinking bool _has_full_module_graph; // Does this CDS archive contain the full archived module graph? + bool _supports_custom_loaders; // int _gc_kind; // Universe::heap()->kind(); char _gc_name[32]; // Universe::heap()->name(); size_t _ptrmap_size_in_bits; // Size of pointer relocation bitmap @@ -210,6 +211,7 @@ class FileMapHeader: private CDSFileMapHeaderBase { int narrow_klass_pointer_bits() const { return _narrow_klass_pointer_bits; } int narrow_klass_shift() const { return _narrow_klass_shift; } bool has_full_module_graph() const { return _has_full_module_graph; } + bool supports_custom_loaders() const { return _supports_custom_loaders; } size_t rw_ptrmap_start_pos() const { return _rw_ptrmap_start_pos; } size_t ro_ptrmap_start_pos() const { return _ro_ptrmap_start_pos; } diff --git a/src/hotspot/share/cds/finalImageRecipes.cpp b/src/hotspot/share/cds/finalImageRecipes.cpp index 46a882a1f88..ff0265dd781 100644 --- a/src/hotspot/share/cds/finalImageRecipes.cpp +++ b/src/hotspot/share/cds/finalImageRecipes.cpp @@ -22,11 +22,14 @@ * */ +#include "cds/aotClassLinker.hpp" #include "cds/aotConstantPoolResolver.hpp" +#include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveUtils.inline.hpp" #include "cds/cdsConfig.hpp" #include "cds/finalImageRecipes.hpp" +#include "cds/unregisteredClasses.hpp" #include "classfile/classLoader.hpp" #include "classfile/javaClasses.hpp" #include "classfile/systemDictionary.hpp" @@ -35,133 +38,340 @@ #include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" #include "oops/constantPool.inline.hpp" +#include "oops/symbol.hpp" #include "runtime/handles.inline.hpp" #include "runtime/mutexLocker.hpp" +#include "utilities/resizableHashTable.hpp" + +static const unsigned INITIAL_TABLE_SIZE = 997; // prime number +static const unsigned MAX_TABLE_SIZE = 10000; GrowableArray* FinalImageRecipes::_tmp_reflect_klasses = nullptr; GrowableArray* FinalImageRecipes::_tmp_reflect_flags = nullptr; GrowableArray* FinalImageRecipes::_tmp_dynamic_proxy_classes = nullptr; static FinalImageRecipes* _final_image_recipes = nullptr; +static void mark_pointers_in_array(Array* array) { + if (array == nullptr) { + return; + } + for (int i = 0; i < array->length(); i++) { + InstanceKlassRecipe* recipe = array->adr_at(i); + recipe->mark_pointers(); + } +} + +class ArchivedCustomLoaderClassRecipesTable { +private: + Symbol* _loader_id; + Array* _class_recipes_list; + + address* loader_id_addr() const { return (address*)&_loader_id; } + address* class_recipes_list_addr() const { return (address*)&_class_recipes_list; } + +public: + void init(Symbol* aot_id, Array* class_recipes_list) { + _loader_id = aot_id; + _class_recipes_list = class_recipes_list; + } + Symbol* loader_id() const { return _loader_id; } + Array* class_recipes_list() const { return _class_recipes_list; } + + void mark_pointers() { + ArchivePtrMarker::mark_pointer(loader_id_addr()); + ArchivePtrMarker::mark_pointer(class_recipes_list_addr()); + mark_pointers_in_array(_class_recipes_list); + } +}; + +inline bool custom_loader_class_recipes_equals(ArchivedCustomLoaderClassRecipesTable* table, Symbol* loader_id, int len_unused) { + return table->loader_id()->equals(loader_id); +} + +class ArchivedCustomLoaderClassRecipesTableMap : public OffsetCompactHashtable +{ +public: + ArchivedCustomLoaderClassRecipesTable* get_class_recipe_list(Symbol* aot_id) { + unsigned int hash = Symbol::symbol_hash(aot_id); + return lookup(aot_id, hash, 0 /* ignored */); + } +}; + +typedef GrowableArrayCHeap ClassRecipeList; + +class ClassLoaderIdToClassRecipesTableMap : public ResizeableHashTable +{ + using ResizeableHashTableBase = ResizeableHashTable; +private: + class CopyClassRecipeTableToArchive : StackObj { + private: + CompactHashtableWriter* _writer; + ArchiveBuilder* _builder; + public: + CopyClassRecipeTableToArchive(CompactHashtableWriter* writer) : _writer(writer), + _builder(ArchiveBuilder::current()) + {} + + bool do_entry(Symbol* loader_id, ClassRecipeList* table) { + ArchivedCustomLoaderClassRecipesTable* tableForLoader = (ArchivedCustomLoaderClassRecipesTable*)ArchiveBuilder::ro_region_alloc(sizeof(ArchivedCustomLoaderClassRecipesTable)); + assert(_builder->has_been_archived(loader_id), "must be"); + Symbol* buffered_loader_id = _builder->get_buffered_addr(loader_id); + tableForLoader->init(buffered_loader_id, ArchiveUtils::archive_array(table)); + tableForLoader->mark_pointers(); + unsigned int hash = Symbol::symbol_hash(loader_id); + _writer->add(hash, AOTCompressedPointers::encode_not_null((address)tableForLoader)); + return true; + } + }; + +public: + ClassLoaderIdToClassRecipesTableMap(unsigned size, unsigned max_size) : ResizeableHashTableBase(size, max_size) {} + + void add_class_recipe(Symbol* loader_id, InstanceKlassRecipe* ikr) { + assert(loader_id != nullptr, "sanity check"); + ClassRecipeList** class_recipe_list_ptr = get(loader_id); + ClassRecipeList* class_recipe_list = nullptr; + if (class_recipe_list_ptr != nullptr) { + class_recipe_list = *class_recipe_list_ptr; + } else { + class_recipe_list = new ClassRecipeList(1000); + put(loader_id, class_recipe_list); + } + class_recipe_list->append(*ikr); + } + + void write_to_archive(ArchivedCustomLoaderClassRecipesTableMap* archived_map, const char* map_name) { + CompactHashtableStats stats; + CompactHashtableWriter writer(number_of_entries(), &stats); + CopyClassRecipeTableToArchive archiver(&writer); + iterate(&archiver); + writer.dump(archived_map, map_name); + } +}; + +ClassLoaderIdToClassRecipesTableMap* _aot_safe_loader_classes_map; +ArchivedCustomLoaderClassRecipesTableMap _archived_aot_safe_loader_classes_map; + void* FinalImageRecipes::operator new(size_t size) throw() { return ArchiveBuilder::current()->ro_region_alloc(size); } +void FinalImageRecipeTable::mark_pointers() { + ArchivePtrMarker::mark_pointer(&_boot1); + mark_pointers_in_array(_boot1); + ArchivePtrMarker::mark_pointer(&_boot2); + mark_pointers_in_array(_boot2); + ArchivePtrMarker::mark_pointer(&_platform); + mark_pointers_in_array(_platform); + ArchivePtrMarker::mark_pointer(&_app); + mark_pointers_in_array(_app); + ArchivePtrMarker::mark_pointer(&_aot_unsafe_custom_loader_classes); + mark_pointers_in_array(_aot_unsafe_custom_loader_classes); +} + void FinalImageRecipes::record_all_classes() { - _all_klasses = ArchiveUtils::archive_array(ArchiveBuilder::current()->klasses()); - ArchivePtrMarker::mark_pointer(&_all_klasses); + _class_table = (FinalImageRecipeTable*)ArchiveBuilder::ro_region_alloc(sizeof(FinalImageRecipeTable)); + ArchivePtrMarker::mark_pointer(&_class_table); + _class_table->set_boot1(write_classes(nullptr, true, true)); + _class_table->set_boot2(write_classes(nullptr, false, true)); + _class_table->set_platform(write_classes(SystemDictionary::java_platform_loader(), false, true)); + _class_table->set_app(write_classes(SystemDictionary::java_system_loader(), false, true)); + _class_table->set_aot_unsafe_custom_loader_classes(write_classes(nullptr, false, false)); + record_aot_safe_custom_loader_classes(); + _class_table->mark_pointers(); } -void FinalImageRecipes::record_recipes_for_constantpool() { +void FinalImageRecipes::record_aot_safe_custom_loader_classes() { ResourceMark rm; + GrowableArray* all_classes = ArchiveBuilder::current()->klasses(); + _aot_safe_loader_classes_map = new (mtClass) ClassLoaderIdToClassRecipesTableMap(INITIAL_TABLE_SIZE, MAX_TABLE_SIZE); + for (int i = 0; i < all_classes->length(); i++) { + Klass* k = all_classes->at(i); + if (k->is_instance_klass()) { + InstanceKlass* ik = InstanceKlass::cast(k); + if (SystemDictionaryShared::is_builtin(ik) || !ik->is_defined_by_aot_safe_custom_loader()) { + continue; + } - // The recipes are recorded regardless of CDSConfig::is_dumping_{invokedynamic,dynamic_proxies,reflection_data}(). - // If some of these options are not enabled, the corresponding recipes will be - // ignored during the final image assembly. + int flags = 0; + Array* cp_recipe = record_recipe_for_constantpool(ik, flags); + InstanceKlassRecipe ikr(ArchiveBuilder::current()->get_buffered_addr(ik), cp_recipe, flags); - GrowableArray*> tmp_cp_recipes; - GrowableArray tmp_flags; + Symbol* loader_id = ik->cl_aot_identity(); + assert(loader_id != nullptr, "must be"); + _aot_safe_loader_classes_map->add_class_recipe(loader_id, &ikr); + } + } + if (log_is_enabled(Info, aot, load)) { + _aot_safe_loader_classes_map->iterate_all([&](Symbol* loader_id, ClassRecipeList* table) { + ResourceMark rm; + for (int i = 0; i < table->length(); i++) { + InstanceKlassRecipe* ikr = table->adr_at(i); + InstanceKlass* ik = ikr->instance_klass(); + log_info(aot, load)("category %s[%d] %s", loader_id->as_C_string(), i, ik->external_name()); + } + }); + } + _aot_safe_loader_classes_map->write_to_archive(&_archived_aot_safe_loader_classes_map, "archived custom loader classes map"); +} - GrowableArray* klasses = ArchiveBuilder::current()->klasses(); - for (int i = 0; i < klasses->length(); i++) { - GrowableArray cp_indices; - int flags = 0; +Array* FinalImageRecipes::write_classes(oop class_loader, bool is_javabase, bool is_builtin_loader) { + ResourceMark rm; + GrowableArray list; + GrowableArray* all_classes = ArchiveBuilder::current()->klasses(); - Klass* k = klasses->at(i); + for (int i = 0; i < all_classes->length(); i++) { + Klass* k = all_classes->at(i); if (k->is_instance_klass()) { InstanceKlass* ik = InstanceKlass::cast(k); - 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 |= CP_RESOLVE_CLASS; - } + if (is_builtin_loader) { + if (ik->class_loader() != class_loader) { + continue; + } + if ((ik->module() == ModuleEntryTable::javabase_moduleEntry()) != is_javabase) { + continue; + } + } else { + // skip builtin loader classes when writing custom loader classes + if (SystemDictionaryShared::is_builtin(ik) || ik->is_defined_by_aot_safe_custom_loader()) { + continue; } } + int flags = 0; + Array* cp_recipe = record_recipe_for_constantpool(ik, flags); + InstanceKlassRecipe recipe(ArchiveBuilder::current()->get_buffered_addr(ik), cp_recipe, flags); + list.append(recipe); + const char* category = AOTClassLinker::class_category_name(ik); + log_info(aot, load)("category %s[%d] %s", category, list.length()-1, ik->external_name()); + } + } - if (cp_cache != nullptr) { - Array* field_entries = cp_cache->resolved_field_entries(); - if (field_entries != nullptr) { - for (int i = 0; i < field_entries->length(); i++) { - ResolvedFieldEntry* rfe = field_entries->adr_at(i); - if (rfe->is_resolved(Bytecodes::_getstatic) || - rfe->is_resolved(Bytecodes::_putstatic) || - rfe->is_resolved(Bytecodes::_getfield) || - rfe->is_resolved(Bytecodes::_putfield)) { - cp_indices.append(rfe->constant_pool_index()); - flags |= CP_RESOLVE_FIELD_AND_METHOD; - } - } - } + if (list.length() == 0) { + return nullptr; + } else { + const char* category = AOTClassLinker::class_category_name(list.adr_at(0)->instance_klass()); + log_info(aot, link)("recorded %d class(es) for category %s", list.length(), category); + return ArchiveUtils::archive_array(&list); + } +} + +Array* FinalImageRecipes::record_recipe_for_constantpool(InstanceKlass* ik, int& flags) { + ConstantPool* cp = ik->constants(); + ConstantPoolCache* cp_cache = cp->cache(); + GrowableArray cp_indices; - Array* method_entries = cp_cache->resolved_method_entries(); - if (method_entries != nullptr) { - for (int i = 0; i < method_entries->length(); i++) { - ResolvedMethodEntry* rme = method_entries->adr_at(i); - if (rme->is_resolved(Bytecodes::_invokevirtual) || - rme->is_resolved(Bytecodes::_invokespecial) || - rme->is_resolved(Bytecodes::_invokeinterface) || - rme->is_resolved(Bytecodes::_invokestatic) || - rme->is_resolved(Bytecodes::_invokehandle)) { - cp_indices.append(rme->constant_pool_index()); - flags |= CP_RESOLVE_FIELD_AND_METHOD; - } - } + 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 |= CP_RESOLVE_CLASS; + } + } + } + + if (cp_cache != nullptr) { + Array* field_entries = cp_cache->resolved_field_entries(); + if (field_entries != nullptr) { + for (int i = 0; i < field_entries->length(); i++) { + ResolvedFieldEntry* rfe = field_entries->adr_at(i); + if (rfe->is_resolved(Bytecodes::_getstatic) || + rfe->is_resolved(Bytecodes::_putstatic) || + rfe->is_resolved(Bytecodes::_getfield) || + rfe->is_resolved(Bytecodes::_putfield)) { + cp_indices.append(rfe->constant_pool_index()); + flags |= CP_RESOLVE_FIELD_AND_METHOD; } + } + } - Array* indy_entries = cp_cache->resolved_indy_entries(); - if (indy_entries != nullptr) { - for (int i = 0; i < indy_entries->length(); i++) { - ResolvedIndyEntry* rie = indy_entries->adr_at(i); - int cp_index = rie->constant_pool_index(); - if (rie->is_resolved()) { - cp_indices.append(cp_index); - flags |= CP_RESOLVE_INDY; - } - } + Array* method_entries = cp_cache->resolved_method_entries(); + if (method_entries != nullptr) { + for (int i = 0; i < method_entries->length(); i++) { + ResolvedMethodEntry* rme = method_entries->adr_at(i); + if (rme->is_resolved(Bytecodes::_invokevirtual) || + rme->is_resolved(Bytecodes::_invokespecial) || + rme->is_resolved(Bytecodes::_invokeinterface) || + rme->is_resolved(Bytecodes::_invokestatic) || + rme->is_resolved(Bytecodes::_invokehandle)) { + cp_indices.append(rme->constant_pool_index()); + flags |= CP_RESOLVE_FIELD_AND_METHOD; } } } - if (cp_indices.length() > 0) { - LogStreamHandle(Trace, aot, resolve) log; - if (log.is_enabled()) { - log.print("ConstantPool entries for %s to be pre-resolved:", k->external_name()); - for (int i = 0; i < cp_indices.length(); i++) { - log.print(" %d", cp_indices.at(i)); + Array* indy_entries = cp_cache->resolved_indy_entries(); + if (indy_entries != nullptr) { + for (int i = 0; i < indy_entries->length(); i++) { + ResolvedIndyEntry* rie = indy_entries->adr_at(i); + int cp_index = rie->constant_pool_index(); + if (rie->is_resolved()) { + cp_indices.append(cp_index); + flags |= CP_RESOLVE_INDY; } - log.print("\n"); } - tmp_cp_recipes.append(ArchiveUtils::archive_array(&cp_indices)); - } else { - tmp_cp_recipes.append(nullptr); } - tmp_flags.append(flags); } - _cp_recipes = ArchiveUtils::archive_array(&tmp_cp_recipes); - ArchivePtrMarker::mark_pointer(&_cp_recipes); + if (cp_indices.length() > 0) { + LogStreamHandle(Trace, aot, resolve) log; + if (log.is_enabled()) { + log.print("ConstantPool entries for %s to be pre-resolved:", ik->external_name()); + for (int i = 0; i < cp_indices.length(); i++) { + log.print(" %d", cp_indices.at(i)); + } + log.print("\n"); + } + return ArchiveUtils::archive_array(&cp_indices); + } else { + return nullptr; + } +} - _flags = ArchiveUtils::archive_array(&tmp_flags); - ArchivePtrMarker::mark_pointer(&_flags); +void FinalImageRecipes::apply_cp_recipes_for_class(JavaThread* current, InstanceKlassRecipe* ikr) { + InstanceKlass* ik = ikr->instance_klass(); + Array* cp_indices = ikr->cp_recipe(); + int flags = ikr->flags(); + if (cp_indices != nullptr) { + if (!strcmp(ik->external_name(), "org.openjdk.aot.testclass.Foo")) { + log_info(cds)("Applying constant pool recipes for %s", ik->external_name()); + } + if (ik->is_loaded()) { + ResourceMark rm(current); + ConstantPool* cp = ik->constants(); + GrowableArray preresolve_list(cp->length(), cp->length(), false); + for (int j = 0; j < cp_indices->length(); j++) { + preresolve_list.at_put(cp_indices->at(j), true); + } + if ((flags & CP_RESOLVE_CLASS) != 0) { + AOTConstantPoolResolver::preresolve_class_cp_entries(current, ik, &preresolve_list); + } + if ((flags & CP_RESOLVE_FIELD_AND_METHOD) != 0) { + AOTConstantPoolResolver::preresolve_field_and_method_cp_entries(current, ik, &preresolve_list); + } + if ((flags & CP_RESOLVE_INDY) != 0) { + AOTConstantPoolResolver::preresolve_indy_cp_entries(current, ik, &preresolve_list); + } + } + } } void FinalImageRecipes::apply_recipes_for_constantpool(JavaThread* current) { assert(CDSConfig::is_dumping_final_static_archive(), "must be"); - for (int i = 0; i < _all_klasses->length(); i++) { - Array* cp_indices = _cp_recipes->at(i); - int flags = _flags->at(i); + _class_table->iterate_builtin_classes([&](InstanceKlassRecipe* ikr) { + apply_cp_recipes_for_class(current, ikr); + InstanceKlass* ik = ikr->instance_klass(); + Array* cp_indices = ikr->cp_recipe(); + int flags = ikr->flags(); if (cp_indices != nullptr) { - InstanceKlass* ik = InstanceKlass::cast(_all_klasses->at(i)); + if (!strcmp(ik->external_name(), "org.openjdk.aot.testclass.Foo")) { + log_info(cds)("Applying constant pool recipes for %s", ik->external_name()); + } if (ik->is_loaded()) { ResourceMark rm(current); ConstantPool* cp = ik->constants(); @@ -180,7 +390,14 @@ void FinalImageRecipes::apply_recipes_for_constantpool(JavaThread* current) { } } } - } + }); + + _archived_aot_safe_loader_classes_map.iterate_all([&](ArchivedCustomLoaderClassRecipesTable* cl_table) { + Array* recipes_list = cl_table->class_recipes_list(); + for (int i = 0; i < recipes_list->length(); i++) { + apply_cp_recipes_for_class(current, recipes_list->adr_at(i)); + } + }); } void FinalImageRecipes::record_recipes_for_reflection_data() { @@ -249,41 +466,190 @@ void FinalImageRecipes::record_recipes_for_dynamic_proxies() { } } -void FinalImageRecipes::load_all_classes(TRAPS) { - assert(CDSConfig::is_dumping_final_static_archive(), "sanity"); - 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()) { - SystemDictionaryShared::init_dumptime_info(ik); - SystemDictionaryShared::add_unregistered_class(THREAD, ik); - SystemDictionaryShared::copy_unregistered_class_size_and_crc32(ik); - } else if (!ik->is_hidden()) { - Klass* actual = SystemDictionary::resolve_or_fail(ik->name(), class_loader, true, CHECK); - if (actual != ik) { - ResourceMark rm(THREAD); - log_error(aot)("Unable to resolve class from CDS archive: %s", ik->external_name()); - log_error(aot)("Expected: " INTPTR_FORMAT ", actual: " INTPTR_FORMAT, p2i(ik), p2i(actual)); - log_error(aot)("Please check if your VM command-line is the same as in the training run"); - AOTMetaspace::unrecoverable_writing_error(); - } - 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"); - ResourceMark rm(THREAD); - log_info(aot, init)("Initializing %s", ik->external_name()); - ik->initialize(CHECK); - } +void FinalImageRecipes::load_builtin_loader_classes(TRAPS) { + precond(CDSConfig::is_dumping_aot_linked_classes()); + + Handle h_platform_loader(THREAD, SystemDictionary::java_platform_loader()); + Handle h_system_loader(THREAD, SystemDictionary::java_system_loader()); + + load_classes_in_table(_class_table->boot1(), "boot1", Handle(), CHECK); + load_classes_in_table(_class_table->boot2(), "boot2", Handle(), CHECK); + + initiate_loading(THREAD, "plat", h_platform_loader, _class_table->boot1()); + initiate_loading(THREAD, "plat", h_platform_loader, _class_table->boot2()); + load_classes_in_table(_class_table->platform(), "plat", h_platform_loader, CHECK); + + initiate_loading(THREAD, "app", h_system_loader, _class_table->boot1()); + initiate_loading(THREAD, "app", h_system_loader, _class_table->boot2()); + initiate_loading(THREAD, "app", h_system_loader, _class_table->platform()); + load_classes_in_table(_class_table->app(), "app", h_system_loader, CHECK); +} + +void FinalImageRecipes::load_aot_safe_custom_loader_classes(TRAPS) { + UnregisteredClasses::initialize(CHECK); + _archived_aot_safe_loader_classes_map.iterate_all([&](ArchivedCustomLoaderClassRecipesTable* cl_table) { + Handle unreg_class_loader = UnregisteredClasses::create_unregistered_loader(THREAD); + SystemDictionary::register_loader(unreg_class_loader); + assert(unreg_class_loader.not_null(), "must be"); + ResourceMark rm; + char* loader_id_str = cl_table->loader_id()->as_C_string(); + + initiate_loading(THREAD, loader_id_str, unreg_class_loader, _class_table->boot1()); + initiate_loading(THREAD, loader_id_str, unreg_class_loader, _class_table->boot2()); + initiate_loading(THREAD, loader_id_str, unreg_class_loader, _class_table->platform()); + initiate_loading(THREAD, loader_id_str, unreg_class_loader, _class_table->app()); + + Array* recipes = cl_table->class_recipes_list(); + load_classes_in_table(recipes, loader_id_str, unreg_class_loader, CHECK); + }); +} + +void FinalImageRecipes::load_aot_unsafe_custom_loader_classes(TRAPS) { + precond(CDSConfig::is_dumping_aot_linked_classes()); + // Use UnregisteredClassLoader to load these classes + UnregisteredClasses::initialize(CHECK); + Handle unreg_class_loader = UnregisteredClasses::unregistered_class_loader(THREAD); + SystemDictionary::register_loader(unreg_class_loader); + assert(unreg_class_loader.not_null(), "must be"); + + initiate_loading(THREAD, "unreg", unreg_class_loader, _class_table->boot1()); + initiate_loading(THREAD, "unreg", unreg_class_loader, _class_table->boot2()); + initiate_loading(THREAD, "unreg", unreg_class_loader, _class_table->platform()); + initiate_loading(THREAD, "unreg", unreg_class_loader, _class_table->app()); + + load_classes_in_table(_class_table->aot_unsafe_custom_loader_classes(), "unreg", unreg_class_loader, CHECK); +} + +void FinalImageRecipes::load_classes_in_table(Array* recipes, + const char* category_name, Handle loader, TRAPS) { + if (recipes == nullptr) { + return; + } + for (int i = 0; i < recipes->length(); i++) { + InstanceKlass* ik = recipes->adr_at(i)->instance_klass(); + if (ik->is_hidden()) { + continue; + } + 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)" : ""); + } + + InstanceKlass* loaded_ik = SystemDictionary::find_instance_klass(THREAD, ik->name(), loader); + if (loaded_ik == nullptr) { + SystemDictionary::preload_class(loader, ik, CHECK); + precond(SystemDictionary::find_instance_klass(THREAD, ik->name(), loader) == ik); + } else { + assert(loaded_ik == ik, "sanity check"); + } + } +} + +// 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. +// +// For example, we initiate the loading of java/lang/String in the AppClassLoader. This will allow +// any App classes to have a pre-resolved ConstantPool entry that references java/lang/String. +// +// TODO: we can limit the number of initiated classes to only those that are actually referenced by +// AOT-linked classes loaded by . +void FinalImageRecipes::initiate_loading(JavaThread* current, const char* category_name, + Handle initiating_loader, Array* recipes) { + if (recipes == nullptr) { + return; + } + +#if 0 + assert(initiating_loader() == SystemDictionary::java_platform_loader() || + initiating_loader() == SystemDictionary::java_system_loader() || + initiating_loader() == UnregisteredClasses::unregistered_class_loader(current)(), "must be"); +#endif + ClassLoaderData* loader_data = ClassLoaderData::class_loader_data(initiating_loader()); + MonitorLocker mu1(SystemDictionary_lock); + + for (int i = 0; i < recipes->length(); i++) { + InstanceKlass* ik = recipes->adr_at(i)->instance_klass(); + assert(ik->is_loaded(), "must have already been loaded by a parent loader"); + assert(ik->class_loader() != initiating_loader(), "must be a parent loader"); + assert(ik->class_loader() == nullptr || + ik->class_loader() == SystemDictionary::java_platform_loader() || + ik->class_loader() == SystemDictionary::java_system_loader(), "must be"); + if (ik->is_public() && !ik->is_hidden()) { + if (log_is_enabled(Info, aot, load)) { + ResourceMark rm(current); + const char* defining_loader = (ik->class_loader() == nullptr ? "boot" : "plat"); + 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); + } + } +} + +void FinalImageRecipes::exit_on_exception(JavaThread* current) { + assert(current->has_pending_exception(), "precondition"); + ResourceMark rm(current); + if (current->pending_exception()->is_a(vmClasses::OutOfMemoryError_klass())) { + log_error(aot)("Out of memory. Please run with a larger Java heap, current MaxHeapSize = " + "%zuM", MaxHeapSize/M); + } else { + oop message = java_lang_Throwable::message(current->pending_exception()); + log_error(aot)("%s: %s", current->pending_exception()->klass()->external_name(), + message == nullptr ? "(no message)" : java_lang_String::as_utf8_string(message)); + } + vm_exit_during_initialization("Unexpected exception when loading aot-linked classes."); +} + +// Some cached heap objects may hold references to methods in aot-linked +// classes (via MemberName). We need to make sure all classes are +// linked before executing any bytecode. +void FinalImageRecipes::link_classes(JavaThread* current) { + link_classes_impl(current); + if (current->has_pending_exception()) { + exit_on_exception(current); + } +} + +void FinalImageRecipes::link_classes_impl(TRAPS) { + //precond(CDSConfig::is_using_aot_linked_classes()); + + link_classes_in_table(_class_table->boot1(), CHECK); + link_classes_in_table(_class_table->boot2(), CHECK); + link_classes_in_table(_class_table->platform(), CHECK); + link_classes_in_table(_class_table->app(), CHECK); + link_classes_in_table(_class_table->aot_unsafe_custom_loader_classes(), CHECK); + _archived_aot_safe_loader_classes_map.iterate_all([&](ArchivedCustomLoaderClassRecipesTable* cl_table) { + link_classes_in_table(cl_table->class_recipes_list(), CHECK); + }); +} + +void FinalImageRecipes::link_classes_in_table(Array* recipes, TRAPS) { + if (recipes != nullptr) { + for (int i = 0; i < recipes->length(); i++) { + // NOTE: CDSConfig::is_preserving_verification_constraints() is required + // when storing ik in the AOT cache. This means we don't have to verify + // ik at all. + // + // Without is_preserving_verification_constraints(), ik->link_class() may cause + // class loading, which may result in invocation of ClassLoader::loadClass() calls, + // which CANNOT happen because we are not ready to execute any Java byecodes yet + // at this point. + InstanceKlass* ik = recipes->adr_at(i)->instance_klass(); + ik->link_class(CHECK); } } } +void FinalImageRecipes::load_and_link_all_classes(TRAPS) { + /* Built-in loader classes come first */ + load_builtin_loader_classes(CHECK); + /* Now load custom loader classes */ + load_aot_safe_custom_loader_classes(CHECK); + load_aot_unsafe_custom_loader_classes(CHECK); + link_classes(THREAD); +} + void FinalImageRecipes::apply_recipes_for_reflection_data(JavaThread* current) { assert(CDSConfig::is_dumping_final_static_archive(), "must be"); @@ -377,7 +743,7 @@ void FinalImageRecipes::record_recipes() { assert(CDSConfig::is_dumping_preimage_static_archive(), "must be"); _final_image_recipes = new FinalImageRecipes(); _final_image_recipes->record_all_classes(); - _final_image_recipes->record_recipes_for_constantpool(); + //_final_image_recipes->record_recipes_for_constantpool(); _final_image_recipes->record_recipes_for_reflection_data(); _final_image_recipes->record_recipes_for_dynamic_proxies(); } @@ -399,12 +765,16 @@ void FinalImageRecipes::apply_recipes(TRAPS) { } void FinalImageRecipes::apply_recipes_impl(TRAPS) { - load_all_classes(CHECK); + //load_all_classes(CHECK); + load_and_link_all_classes(CHECK); apply_recipes_for_constantpool(THREAD); apply_recipes_for_reflection_data(CHECK); apply_recipes_for_dynamic_proxies(CHECK); } void FinalImageRecipes::serialize(SerializeClosure* soc) { - soc->do_ptr((void**)&_final_image_recipes); + if (CDSConfig::is_dumping_preimage_static_archive() || (CDSConfig::is_dumping_final_static_archive() && soc->reading())) { + _archived_aot_safe_loader_classes_map.serialize_header(soc); + } +soc->do_ptr((void**)&_final_image_recipes); } diff --git a/src/hotspot/share/cds/finalImageRecipes.hpp b/src/hotspot/share/cds/finalImageRecipes.hpp index 9638fc954bc..b744b88f5af 100644 --- a/src/hotspot/share/cds/finalImageRecipes.hpp +++ b/src/hotspot/share/cds/finalImageRecipes.hpp @@ -34,6 +34,78 @@ class Klass; template class GrowableArray; template class Array; +class InstanceKlassRecipe { +private: + InstanceKlass* _ik; + Array* _cp_recipe; + int _flags; +public: + InstanceKlassRecipe() : _ik(nullptr), _cp_recipe(nullptr), _flags(0) {} // required by GrowableArray + InstanceKlassRecipe(InstanceKlass* ik, Array* cp_recipe, int flags) : + _ik(ik), _cp_recipe(cp_recipe), _flags(flags) {} + + InstanceKlass* instance_klass() const { return _ik; } + Array* cp_recipe() const { return _cp_recipe; } + int flags() const { return _flags; } + + void mark_pointers() { + ArchivePtrMarker::mark_pointer(&_ik); + ArchivePtrMarker::mark_pointer(&_cp_recipe); + } +}; + +class FinalImageRecipeTable { +private: + 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; + + Array* _aot_unsafe_custom_loader_classes; + + template + void iterate_array(Function fn, Array* array) { + if (array != nullptr) { + for (int i = 0; i < array->length(); i++) { + fn(array->adr_at(i)); + } + } + } + +public: + FinalImageRecipeTable() : + _boot1(nullptr), _boot2(nullptr), + _platform(nullptr), _app(nullptr), + _aot_unsafe_custom_loader_classes(nullptr) {} + + Array* boot1() const { return _boot1; } + Array* boot2() const { return _boot2; } + Array* platform() const { return _platform; } + Array* app() const { return _app; } + Array* aot_unsafe_custom_loader_classes() const { return _aot_unsafe_custom_loader_classes; } + + 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; } + void set_aot_unsafe_custom_loader_classes(Array* value) { _aot_unsafe_custom_loader_classes = value; } + + template + void iterate_builtin_classes(Function fn) { + iterate_array(fn, _boot1); + iterate_array(fn, _boot2); + iterate_array(fn, _platform); + iterate_array(fn, _app); + } + template + void iterate_all_classes(Function fn) { + iterate_builtin_classes(fn); + iterate_array(fn, _aot_unsafe_custom_loader_classes); + } + void mark_pointers(); +}; + // This class is used for transferring information from the AOTConfiguration file (aka the "preimage") // to the JVM that creates the AOTCache (aka the "final image"). // - The recipes are recorded when CDSConfig::is_dumping_preimage_static_archive() is true. @@ -49,7 +121,8 @@ class FinalImageRecipes { // A list of all the archived classes from the preimage. We want to transfer all of these // into the final image. - Array* _all_klasses; + //Array* _all_klasses; + FinalImageRecipeTable* _class_table; // 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. @@ -81,7 +154,7 @@ class FinalImageRecipes { static GrowableArray* _tmp_dynamic_proxy_classes; - FinalImageRecipes() : _all_klasses(nullptr), _cp_recipes(nullptr), _flags(nullptr), + FinalImageRecipes() : _class_table(nullptr), _cp_recipes(nullptr), _flags(nullptr), _reflect_klasses(nullptr), _reflect_flags(nullptr), _dynamic_proxy_classes(nullptr) {} @@ -89,17 +162,33 @@ class FinalImageRecipes { // Called when dumping preimage void record_all_classes(); - void record_recipes_for_constantpool(); + void record_aot_safe_custom_loader_classes(); + Array* record_recipe_for_constantpool(InstanceKlass* ik, int& flags); + //void record_recipes_for_constantpool(); void record_recipes_for_reflection_data(); void record_recipes_for_dynamic_proxies(); // Called when dumping final image + void load_builtin_loader_classes(TRAPS); + void load_aot_safe_custom_loader_classes(TRAPS); + void load_aot_unsafe_custom_loader_classes(TRAPS); + void load_classes_in_table(Array* classes, const char* category_name, Handle loader, TRAPS); + void initiate_loading(JavaThread* current, const char* category_name, Handle initiating_loader, Array* classes); + void link_classes(JavaThread* current); + void link_classes_impl(TRAPS); + void link_classes_in_table(Array* classes, TRAPS); + void apply_recipes_impl(TRAPS); - void load_all_classes(TRAPS); + void load_and_link_all_classes(TRAPS); + void apply_cp_recipes_for_class(JavaThread* current, InstanceKlassRecipe* ikr); void apply_recipes_for_constantpool(JavaThread* current); void apply_recipes_for_reflection_data(JavaThread* current); void apply_recipes_for_dynamic_proxies(TRAPS); + Array* write_classes(oop class_loader, bool is_javabase, bool is_builtin_loader); + + static void exit_on_exception(JavaThread* current); + public: static void serialize(SerializeClosure* soc); diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/heapShared.cpp index 24a5570eafb..d38f33a48a5 100644 --- a/src/hotspot/share/cds/heapShared.cpp +++ b/src/hotspot/share/cds/heapShared.cpp @@ -624,8 +624,8 @@ bool HeapShared::archive_object(oop obj, oop referrer, KlassSubGraphInfo* subgra ResourceMark rm; LogTarget(Debug, aot, heap) log; LogStream out(log); - out.print("Archived heap object " PTR_FORMAT " : %s ", - p2i(obj), obj->klass()->external_name()); + out.print("Archived heap object " PTR_FORMAT " : %s, referrer: " PTR_FORMAT, + p2i(obj), obj->klass()->external_name(), p2i(referrer)); if (java_lang_Class::is_instance(obj)) { Klass* k = java_lang_Class::as_Klass(obj); if (k != nullptr) { @@ -1083,12 +1083,16 @@ void KlassSubGraphInfo::add_subgraph_object_klass(Klass* orig_k) { #ifdef ASSERT InstanceKlass* ik = InstanceKlass::cast(orig_k); if (CDSConfig::is_dumping_method_handles()) { - // -XX:AOTInitTestClass must be used carefully in regression tests to - // include only classes that are safe to aot-initialize. - assert(ik->class_loader() == nullptr || - HeapShared::is_lambda_proxy_klass(ik) || - AOTClassInitializer::has_test_class(), - "we can archive only instances of boot classes or lambda proxy classes"); + // TODO: When custom loaders are supported, the custom loader instance needs to be stored in AOTCache + // which is loaded by app or system class loader. So the assert does not hold true. + if (!CDSConfig::supports_custom_loaders()) { + // -XX:AOTInitTestClass must be used carefully in regression tests to + // include only classes that are safe to aot-initialize. + assert(ik->class_loader() == nullptr || + HeapShared::is_lambda_proxy_klass(ik) || + AOTClassInitializer::has_test_class(), + "we can archive only instances of boot classes or lambda proxy classes"); + } } else { assert(ik->class_loader() == nullptr, "must be boot class"); } diff --git a/src/hotspot/share/cds/runTimeClassInfo.hpp b/src/hotspot/share/cds/runTimeClassInfo.hpp index d63a04698bb..f8bc49ff729 100644 --- a/src/hotspot/share/cds/runTimeClassInfo.hpp +++ b/src/hotspot/share/cds/runTimeClassInfo.hpp @@ -268,12 +268,16 @@ class RunTimeClassInfo { // Used by RunTimeSharedDictionary to implement OffsetCompactHashtable::EQUALS static inline bool EQUALS( - const RunTimeClassInfo* value, Symbol* key, int len_unused) { + const RunTimeClassInfo* value, Symbol* key, int is_aot_unsafe_loader_class) { #if INCLUDE_CDS - return (value->klass()->name() == key); -#else - return false; + if (value->klass()->name() == key) { + if (is_aot_unsafe_loader_class) { + return !value->klass()->is_defined_by_aot_safe_custom_loader(); + } + return true; + } #endif + return false; } }; diff --git a/src/hotspot/share/cds/unregisteredClasses.cpp b/src/hotspot/share/cds/unregisteredClasses.cpp index 51b35899599..08d6392f321 100644 --- a/src/hotspot/share/cds/unregisteredClasses.cpp +++ b/src/hotspot/share/cds/unregisteredClasses.cpp @@ -38,6 +38,7 @@ static InstanceKlass* _UnregisteredClassLoader_klass; static InstanceKlass* _UnregisteredClassLoader_Source_klass; static OopHandle _unregistered_class_loader; +static GrowableArray* _unregistered_class_loader_list; void UnregisteredClasses::initialize(TRAPS) { if (_UnregisteredClassLoader_klass != nullptr) { @@ -61,6 +62,20 @@ void UnregisteredClasses::initialize(TRAPS) { const Handle cl = JavaCalls::construct_new_instance(_UnregisteredClassLoader_klass, vmSymbols::void_method_signature(), CHECK); _unregistered_class_loader = OopHandle(Universe::vm_global(), cl()); + _unregistered_class_loader_list = new (mtClassShared)GrowableArray(10, mtClassShared); +} + +Handle UnregisteredClasses::create_unregistered_loader(TRAPS) { + assert(_UnregisteredClassLoader_klass != nullptr, "missing call to UnregisteredClasses::initialize"); + const Handle cl = JavaCalls::construct_new_instance(_UnregisteredClassLoader_klass, + vmSymbols::void_method_signature(), CHECK_NH); + OopHandle cl_handle = OopHandle(Universe::vm_global(), cl()); + _unregistered_class_loader_list->append(cl_handle); + return cl; +} + +Handle UnregisteredClasses::unregistered_class_loader(Thread* current) { + return Handle(current, _unregistered_class_loader.resolve()); } // Load the class of the given name from the location given by path. The path is specified by diff --git a/src/hotspot/share/cds/unregisteredClasses.hpp b/src/hotspot/share/cds/unregisteredClasses.hpp index d61c3462504..3b39670159b 100644 --- a/src/hotspot/share/cds/unregisteredClasses.hpp +++ b/src/hotspot/share/cds/unregisteredClasses.hpp @@ -35,8 +35,10 @@ class UnregisteredClasses: AllStatic { public: static InstanceKlass* load_class(Symbol* name, const char* path, TRAPS); static void initialize(TRAPS); + static Handle create_unregistered_loader(TRAPS); // Returns true if the class is loaded internally for dumping unregistered classes. static bool check_for_exclusion(const InstanceKlass* k); + static Handle unregistered_class_loader(Thread* current); }; #endif // SHARE_CDS_UNREGISTEREDCLASSES_HPP diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index c1f00cbe536..b74b1ac013f 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -5068,6 +5068,9 @@ void ClassFileParser::fill_instance_klass(InstanceKlass* ik, // Set name and CLD before adding to CLD ik->set_class_loader_data(_loader_data); + if (CDSConfig::supports_custom_loaders() && _loader_data != nullptr) { + ik->set_cl_aot_identity(_loader_data->aot_identity()); + } ik->set_class_loader_type(); ik->set_name(_class_name); diff --git a/src/hotspot/share/classfile/classLoaderData.cpp b/src/hotspot/share/classfile/classLoaderData.cpp index dfc3b74db96..f385ffda72c 100644 --- a/src/hotspot/share/classfile/classLoaderData.cpp +++ b/src/hotspot/share/classfile/classLoaderData.cpp @@ -46,8 +46,11 @@ // The bootstrap loader (represented by null) also has a ClassLoaderData, // the singleton class the_null_class_loader_data(). +#include "cds/cdsConfig.hpp" +#include "cds/aotLinkedClassBulkLoader.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/classLoaderDataGraph.inline.hpp" +#include "classfile/classLoaderDataShared.hpp" #include "classfile/dictionary.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/moduleEntry.hpp" @@ -89,7 +92,6 @@ void ClassLoaderData::init_null_class_loader_data() { _the_null_class_loader_data = new ClassLoaderData(Handle(), false); ClassLoaderDataGraph::_head = _the_null_class_loader_data; assert(_the_null_class_loader_data->is_the_null_class_loader_data(), "Must be"); - LogTarget(Trace, class, loader, data) lt; if (lt.is_enabled()) { ResourceMark rm; @@ -149,7 +151,8 @@ ClassLoaderData::ClassLoaderData(Handle h_class_loader, bool has_class_mirror_ho _deallocate_list(nullptr), _next(nullptr), _unloading_next(nullptr), - _class_loader_klass(nullptr), _name(nullptr), _name_and_id(nullptr) { + _class_loader_klass(nullptr), _name(nullptr), _name_and_id(nullptr), + _aot_identity(nullptr) { if (!h_class_loader.is_null()) { _class_loader = _handles.add(h_class_loader()); @@ -1125,3 +1128,9 @@ bool ClassLoaderData::contains_klass(Klass* klass) { } return false; } + +Symbol* ClassLoaderData::parent_aot_identity() const { + oop parent = java_lang_ClassLoader::parent(class_loader()); + ClassLoaderData* parent_cld = java_lang_ClassLoader::loader_data(parent); + return parent_cld->aot_identity(); +} diff --git a/src/hotspot/share/classfile/classLoaderData.hpp b/src/hotspot/share/classfile/classLoaderData.hpp index 64fcfb7519f..b81c75cb2e7 100644 --- a/src/hotspot/share/classfile/classLoaderData.hpp +++ b/src/hotspot/share/classfile/classLoaderData.hpp @@ -174,6 +174,7 @@ class ClassLoaderData : public CHeapObj { Klass* _class_loader_klass; Symbol* _name; Symbol* _name_and_id; + Symbol* _aot_identity; JFR_ONLY(DEFINE_TRACE_ID_FIELD;) void set_next(ClassLoaderData* next); @@ -358,6 +359,9 @@ class ClassLoaderData : public CHeapObj { // Obtain the class loader's _name_and_id, works during unloading. const char* loader_name_and_id() const; Symbol* name_and_id() const { return _name_and_id; } + Symbol* aot_identity() const { return _aot_identity; } + void set_aot_identity(Symbol* aot_id) { _aot_identity = aot_id; } + Symbol* parent_aot_identity() const; unsigned identity_hash() const { return (unsigned)((uintptr_t)this >> LogBytesPerWord); diff --git a/src/hotspot/share/classfile/compactHashtable.hpp b/src/hotspot/share/classfile/compactHashtable.hpp index 1711c5f8cd3..9a99b9a93e1 100644 --- a/src/hotspot/share/classfile/compactHashtable.hpp +++ b/src/hotspot/share/classfile/compactHashtable.hpp @@ -37,7 +37,7 @@ template < typename K, typename V, V (*DECODE)(address base_address, u4 encoded_value), - bool (*EQUALS)(V value, K key, int len) + bool (*EQUALS)(V value, K key, int user_data) > class CompactHashtable; class NumberSeq; @@ -260,7 +260,7 @@ template < typename K, typename V, V (*DECODE)(address base_address, u4 encoded_value), - bool (*EQUALS)(V value, K key, int len) + bool (*EQUALS)(V value, K key, int user_data) > class CompactHashtable : public SimpleCompactHashtable { @@ -270,7 +270,7 @@ class CompactHashtable : public SimpleCompactHashtable { public: // Lookup a value V from the compact table using key K - inline V lookup(K key, unsigned int hash, int len) const { + inline V lookup(K key, unsigned int hash, int user_data) const { if (_entry_count > 0) { int index = hash % _bucket_count; u4 bucket_info = _buckets[index]; @@ -280,7 +280,7 @@ class CompactHashtable : public SimpleCompactHashtable { if (bucket_type == VALUE_ONLY_BUCKET_TYPE) { V value = decode(entry[0]); - if (EQUALS(value, key, len)) { + if (EQUALS(value, key, user_data)) { return value; } } else { @@ -292,7 +292,7 @@ class CompactHashtable : public SimpleCompactHashtable { unsigned int h = (unsigned int)(entry[0]); if (h == hash) { V value = decode(entry[1]); - if (EQUALS(value, key, len)) { + if (EQUALS(value, key, user_data)) { return value; } } @@ -385,7 +385,7 @@ inline V read_value_from_compact_hashtable(address base_address, u4 narrowp) { template < typename K, typename V, - bool (*EQUALS)(V value, K key, int len) + bool (*EQUALS)(V value, K key, int user_data) > class OffsetCompactHashtable : public CompactHashtable< K, V, read_value_from_compact_hashtable, EQUALS> { diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index c11ae1da65c..822bcf5babe 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -1195,6 +1195,7 @@ void java_lang_Class::create_mirror(Klass* k, Handle class_loader, // latter may contain dumptime-specific information that cannot be archived // (e.g., ClassLoaderData*, or static fields that are modified by Java code execution). void java_lang_Class::create_scratch_mirror(Klass* k, TRAPS) { + //if (k->cl_aot_identity() == nullptr && if (k->class_loader() != nullptr && k->class_loader() != SystemDictionary::java_platform_loader() && k->class_loader() != SystemDictionary::java_system_loader()) { @@ -4751,6 +4752,7 @@ int java_lang_ClassLoader::_name_offset; int java_lang_ClassLoader::_nameAndId_offset; int java_lang_ClassLoader::_unnamedModule_offset; int java_lang_ClassLoader::_parent_offset; +int java_lang_ClassLoader::_aotIdentity_offset; ClassLoaderData* java_lang_ClassLoader::loader_data_acquire(oop loader) { assert(loader != nullptr, "loader must not be null"); @@ -4775,7 +4777,8 @@ void java_lang_ClassLoader::release_set_loader_data(oop loader, ClassLoaderData* macro(_name_offset, k1, vmSymbols::name_name(), string_signature, false); \ macro(_nameAndId_offset, k1, "nameAndId", string_signature, false); \ macro(_unnamedModule_offset, k1, "unnamedModule", module_signature, false); \ - macro(_parent_offset, k1, "parent", classloader_signature, false) + macro(_parent_offset, k1, "parent", classloader_signature, false); \ + macro(_aotIdentity_offset, k1, "aotIdentity", string_signature, false); void java_lang_ClassLoader::compute_offsets() { InstanceKlass* k1 = vmClasses::ClassLoader_klass(); @@ -4819,6 +4822,11 @@ oop java_lang_ClassLoader::nameAndId(oop loader) { return loader->obj_field(_nameAndId_offset); } +oop java_lang_ClassLoader::aotIdentity(oop loader) { + assert(is_instance(loader), "loader must be oop"); + return loader->obj_field(_aotIdentity_offset); +} + bool java_lang_ClassLoader::isAncestor(oop loader, oop cl) { assert(is_instance(loader), "loader must be oop"); assert(cl == nullptr || is_instance(cl), "cl argument must be oop"); diff --git a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp index 475e3097e54..400a83d13a3 100644 --- a/src/hotspot/share/classfile/javaClasses.hpp +++ b/src/hotspot/share/classfile/javaClasses.hpp @@ -1485,6 +1485,7 @@ class java_lang_ClassLoader : AllStatic { static int _name_offset; static int _nameAndId_offset; static int _unnamedModule_offset; + static int _aotIdentity_offset; static void compute_offsets(); @@ -1501,6 +1502,7 @@ class java_lang_ClassLoader : AllStatic { static oop parent_no_keepalive(oop loader); static oop name(oop loader); static oop nameAndId(oop loader); + static oop aotIdentity(oop loader); static bool isAncestor(oop loader, oop cl); // Support for parallelCapable field diff --git a/src/hotspot/share/classfile/moduleEntry.cpp b/src/hotspot/share/classfile/moduleEntry.cpp index c109c4df01b..f4d162d09b5 100644 --- a/src/hotspot/share/classfile/moduleEntry.cpp +++ b/src/hotspot/share/classfile/moduleEntry.cpp @@ -416,7 +416,7 @@ void ModuleEntry::metaspace_pointers_do(MetaspaceClosure* it) { #if INCLUDE_CDS_JAVA_HEAP bool ModuleEntry::should_be_archived() const { - return SystemDictionaryShared::is_builtin_loader(loader_data()); + return SystemDictionaryShared::is_builtin_loader(loader_data()) || loader_data()->aot_identity() != nullptr; } void ModuleEntry::remove_unshareable_info() { diff --git a/src/hotspot/share/classfile/modules.cpp b/src/hotspot/share/classfile/modules.cpp index 633b1e6bc90..8c262d8df7b 100644 --- a/src/hotspot/share/classfile/modules.cpp +++ b/src/hotspot/share/classfile/modules.cpp @@ -505,7 +505,7 @@ void Modules::check_archived_module_oop(oop orig_module_obj) { // We only archive the default module graph, which should contain only java.lang.Module oops // for the 3 built-in loaders (boot/platform/system) ClassLoaderData* loader_data = orig_module_ent->loader_data(); - assert(loader_data->is_builtin_class_loader_data(), "must be"); + assert(loader_data->is_builtin_class_loader_data() || loader_data->aot_identity() != nullptr, "must be"); precond(ArchiveBuilder::current()->has_been_archived(orig_module_ent)); if (orig_module_ent->name() == nullptr) { @@ -522,7 +522,7 @@ void Modules::check_archived_module_oop(oop orig_module_obj) { assert(!_seen_system_unnamed_module, "only once"); _seen_system_unnamed_module = true; } else { - ShouldNotReachHere(); + //ShouldNotReachHere(); } } } diff --git a/src/hotspot/share/classfile/systemDictionary.cpp b/src/hotspot/share/classfile/systemDictionary.cpp index 497884a4ac6..5c647ed6aa5 100644 --- a/src/hotspot/share/classfile/systemDictionary.cpp +++ b/src/hotspot/share/classfile/systemDictionary.cpp @@ -23,7 +23,9 @@ */ #include "cds/aotClassLocation.hpp" +#include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/cdsConfig.hpp" +#include "cds/cdsProtectionDomain.hpp" #include "cds/heapShared.hpp" #include "classfile/classFileParser.hpp" #include "classfile/classFileStream.hpp" @@ -192,8 +194,9 @@ ClassLoaderData* SystemDictionary::register_loader(Handle class_loader, bool cre // Add a new class loader data to the graph. return ClassLoaderDataGraph::add(class_loader, true); } else { - return (class_loader() == nullptr) ? ClassLoaderData::the_null_class_loader_data() : + ClassLoaderData* loader_data = (class_loader() == nullptr) ? ClassLoaderData::the_null_class_loader_data() : ClassLoaderDataGraph::find_or_create(class_loader); + return loader_data; } } @@ -1165,10 +1168,10 @@ void SystemDictionary::load_shared_class_misc(InstanceKlass* ik, ClassLoaderData // - 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(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(class_loader() == nullptr || class_loader() == java_platform_loader() ||class_loader() == java_system_loader() || ik->cl_aot_identity() != nullptr); + //precond(CDSConfig::is_using_aot_linked_classes()); precond(AOTMetaspace::in_aot_cache_static_region((void*)ik)); precond(!ik->is_loaded()); @@ -1189,14 +1192,39 @@ void SystemDictionary::preload_class(Handle class_loader, InstanceKlass* ik, TRA EventClassLoad class_load_event; ClassLoaderData* loader_data = ClassLoaderData::class_loader_data(class_loader()); - oop java_mirror = ik->archived_java_mirror(); - precond(java_mirror != nullptr); - assert(java_lang_Class::module(java_mirror) != nullptr, "must have been archived"); - - 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"); + Handle pd; + PackageEntry* pkg_entry = nullptr; + if (ik->has_archived_mirror_index()) { + oop java_mirror = ik->archived_java_mirror(); + precond(java_mirror != nullptr); + + if (CDSConfig::is_dumping_aot_linked_classes()) { + // in assembly phase + assert(java_lang_Class::module(java_mirror) == nullptr, "must not have been archived"); + assert(ik->package() == nullptr, "must not have been archived"); + + pkg_entry = CDSProtectionDomain::get_package_entry_from_class(ik, class_loader); + // pd for classes loaded by boot loader is null + if (class_loader() != nullptr) { + if (!ik->name()->starts_with("jdk/proxy")) // java/lang/reflect/Proxy$ProxyBuilder defines the proxy classes with a null protection domain. + { + pd = CDSProtectionDomain::init_security_info(class_loader, ik, pkg_entry, CHECK); + } + } + } else { + // in production phase + assert(java_lang_Class::module(java_mirror) != nullptr, "must have been archived"); + + pd = Handle(THREAD, java_lang_Class::protection_domain(java_mirror)); + pkg_entry = ik->package(); + if (is_builtin_class_loader(class_loader())) { + assert(pkg_entry != nullptr || ClassLoader::package_from_class_name(ik->name()) == nullptr, + "non-empty packages for builtin loaders must have been archived"); + } else if (ik->is_defined_by_aot_safe_custom_loader()) { + assert(pkg_entry == nullptr, "packages for aot-safe custom loaders are not archived"); + } + } + } // TODO: the following assert requires JDK-8365580 // assert(is_shared_class_visible(ik->name(), ik, pkg_entry, class_loader), "must be"); @@ -1300,7 +1328,8 @@ InstanceKlass* SystemDictionary::load_instance_class_impl(Symbol* class_name, Ha if (CDSConfig::is_using_archive()) { PerfTraceElapsedTime vmtimer(ClassLoader::perf_shared_classload_time()); - InstanceKlass* ik = SystemDictionaryShared::find_builtin_class(class_name); + InstanceKlass* ik = nullptr; + ik = SystemDictionaryShared::find_builtin_class(class_name); if (ik != nullptr && ik->defined_by_boot_loader() && !ik->shared_loading_failed()) { SharedClassLoadingMark slm(THREAD, ik); k = load_shared_class(ik, class_loader, Handle(), nullptr, pkg_entry, CHECK_NULL); @@ -1628,6 +1657,7 @@ void SystemDictionary::initialize(TRAPS) { SystemDictionaryShared::initialize(); if (CDSConfig::is_dumping_archive()) { AOTClassLocationConfig::dumptime_init(THREAD); + URLClassLoaderClasspathSupport::init(); } #endif // Resolve basic classes @@ -1725,7 +1755,7 @@ void SystemDictionary::update_dictionary(JavaThread* current, void SystemDictionary::add_to_initiating_loader(JavaThread* current, InstanceKlass* k, ClassLoaderData* loader_data) { - assert(CDSConfig::is_using_aot_linked_classes(), "must be"); + //assert(CDSConfig::is_using_aot_linked_classes(), "must be"); assert_locked_or_safepoint(SystemDictionary_lock); Symbol* name = k->name(); Dictionary* dictionary = loader_data->dictionary(); diff --git a/src/hotspot/share/classfile/systemDictionary.hpp b/src/hotspot/share/classfile/systemDictionary.hpp index e32e0082f8f..e6f8616d0cd 100644 --- a/src/hotspot/share/classfile/systemDictionary.hpp +++ b/src/hotspot/share/classfile/systemDictionary.hpp @@ -77,6 +77,7 @@ template class GrowableArray; class SystemDictionary : AllStatic { friend class AOTLinkedClassBulkLoader; + friend class FinalImageRecipes; friend class BootstrapInfo; friend class LambdaProxyClassDictionary; friend class vmClasses; diff --git a/src/hotspot/share/classfile/systemDictionaryShared.cpp b/src/hotspot/share/classfile/systemDictionaryShared.cpp index 03025666264..11423de76b0 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.cpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.cpp @@ -46,6 +46,7 @@ #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/classLoaderDataGraph.hpp" +#include "classfile/classLoaderDataShared.hpp" #include "classfile/dictionary.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/symbolTable.hpp" @@ -106,7 +107,7 @@ InstanceKlass* SystemDictionaryShared::load_shared_class_for_builtin_loader( if (ik != nullptr && !ik->shared_loading_failed()) { if ((SystemDictionary::is_system_class_loader(class_loader()) && ik->defined_by_app_loader()) || - (SystemDictionary::is_platform_class_loader(class_loader()) && ik->defined_by_platform_loader())) { + (SystemDictionary::is_platform_class_loader(class_loader()) && ik->defined_by_platform_loader())) { SharedClassLoadingMark slm(THREAD, ik); PackageEntry* pkg_entry = CDSProtectionDomain::get_package_entry_from_class(ik, class_loader); Handle protection_domain; @@ -141,7 +142,7 @@ InstanceKlass* SystemDictionaryShared::lookup_from_stream(Symbol* class_name, const RunTimeClassInfo* record = find_record(&_static_archive._unregistered_dictionary, &_dynamic_archive._unregistered_dictionary, - class_name); + class_name, /*is_aot_unsafe_loader_class*/ true); if (record == nullptr) { return nullptr; } @@ -628,14 +629,12 @@ InstanceKlass* SystemDictionaryShared::find_or_load_shared_class( if (!has_platform_or_app_classes()) { return nullptr; } - if (SystemDictionary::is_system_class_loader(class_loader()) || SystemDictionary::is_platform_class_loader(class_loader())) { ClassLoaderData *loader_data = register_loader(class_loader); Dictionary* dictionary = loader_data->dictionary(); - // Note: currently, find_or_load_shared_class is called only from - // JVM_FindLoadedClass and used for PlatformClassLoader and AppClassLoader, + // Note: currently, find_or_load_shared_class is called for PlatformClassLoader and AppClassLoader, // which are parallel-capable loaders, so a lock here is NOT taken. assert(get_loader_lock_or_null(class_loader) == nullptr, "ObjectLocker not required"); { @@ -647,13 +646,13 @@ InstanceKlass* SystemDictionaryShared::find_or_load_shared_class( } k = load_shared_class_for_builtin_loader(name, class_loader, THREAD); - if (k != nullptr) { - SharedClassLoadingMark slm(THREAD, k); - k = find_or_define_instance_class(name, class_loader, k, CHECK_NULL); - } } } + if (k != nullptr) { + SharedClassLoadingMark slm(THREAD, k); + k = find_or_define_instance_class(name, class_loader, k, CHECK_NULL); + } DEBUG_ONLY(check_klass_after_loading(k);) return k; @@ -699,8 +698,9 @@ void SystemDictionaryShared::copy_unregistered_class_size_and_crc32(InstanceKlas precond(klass->in_aot_cache()); // A shared class must have a RunTimeClassInfo record - const RunTimeClassInfo* record = find_record(&_static_archive._unregistered_dictionary, - nullptr, klass->name()); + //const RunTimeClassInfo* record = find_record(&_static_archive._unregistered_dictionary, + // nullptr, klass->name()); + const RunTimeClassInfo* record = RunTimeClassInfo::get_for(klass); precond(record != nullptr); precond(record->klass() == klass); @@ -777,6 +777,10 @@ void SystemDictionaryShared::init_dumptime_info_from_preimage(InstanceKlass* k) } else if (SystemDictionary::is_system_class_loader(k->class_loader())) { AOTClassLocationConfig::dumptime_set_has_app_classes(); } + + if (k->defined_by_other_loaders() && !k->is_defined_by_aot_safe_custom_loader()) { + SystemDictionaryShared::copy_unregistered_class_size_and_crc32(k); + } } // Check if a class or any of its supertypes has been redefined. @@ -827,7 +831,7 @@ class UnregisteredClassesDuplicationChecker : StackObj { UnregisteredClassesDuplicationChecker() : _thread(Thread::current()) {} void do_entry(InstanceKlass* k, DumpTimeClassInfo& info) { - if (!SystemDictionaryShared::is_builtin(k)) { + if (!SystemDictionaryShared::is_builtin(k) && !k->is_defined_by_aot_safe_custom_loader()) { _list.append(k); } } @@ -1339,7 +1343,8 @@ class CopySharedClassInfoToArchive : StackObj { } if (log_is_enabled(Trace, aot, hashtables)) { ResourceMark rm; - log_trace(aot, hashtables)("%s dictionary: %s", (_is_builtin ? "builtin" : "unregistered"), info._klass->external_name()); + const char* dict_name = _is_builtin ? "builtin" : "unregistered"; + log_trace(aot, hashtables)("%s dictionary: %s", dict_name, info._klass->external_name()); } // Save this for quick runtime lookup of InstanceKlass* -> RunTimeClassInfo* @@ -1357,7 +1362,8 @@ void SystemDictionaryShared::write_dictionary(RunTimeSharedDictionary* dictionar CopySharedClassInfoToArchive copy(&writer, is_builtin); assert_lock_strong(DumpTimeTable_lock); _dumptime_table->iterate_all_live_classes(©); - writer.dump(dictionary, is_builtin ? "builtin dictionary" : "unregistered dictionary"); + const char* dict_name = is_builtin ? "builtin" : "unregistered"; + writer.dump(dictionary, dict_name); } void SystemDictionaryShared::write_to_archive(bool is_static_archive) { @@ -1388,7 +1394,7 @@ void SystemDictionaryShared::serialize_vm_classes(SerializeClosure* soc) { } const RunTimeClassInfo* -SystemDictionaryShared::find_record(RunTimeSharedDictionary* static_dict, RunTimeSharedDictionary* dynamic_dict, Symbol* name) { +SystemDictionaryShared::find_record(RunTimeSharedDictionary* static_dict, RunTimeSharedDictionary* dynamic_dict, Symbol* name, bool is_aot_unsafe_loader_class) { if (!CDSConfig::is_using_archive() || !name->in_aot_cache()) { // The names of all shared classes must also be a shared Symbol. return nullptr; @@ -1410,11 +1416,11 @@ SystemDictionaryShared::find_record(RunTimeSharedDictionary* static_dict, RunTim if (!AOTMetaspace::in_aot_cache_dynamic_region(name)) { // The names of all shared classes in the static dict must also be in the // static archive - record = static_dict->lookup(name, hash, 0); + record = static_dict->lookup(name, hash, (int)is_aot_unsafe_loader_class); } if (record == nullptr && DynamicArchive::is_mapped()) { - record = dynamic_dict->lookup(name, hash, 0); + record = dynamic_dict->lookup(name, hash, (int)is_aot_unsafe_loader_class); } return record; @@ -1423,7 +1429,7 @@ SystemDictionaryShared::find_record(RunTimeSharedDictionary* static_dict, RunTim InstanceKlass* SystemDictionaryShared::find_builtin_class(Symbol* name) { const RunTimeClassInfo* record = find_record(&_static_archive._builtin_dictionary, &_dynamic_archive._builtin_dictionary, - name); + name, /*is_aot_unsafe_loader_class*/ false); if (record != nullptr) { assert(!record->klass()->is_hidden(), "hidden class cannot be looked up by name"); DEBUG_ONLY(check_klass_after_loading(record->klass());) @@ -1466,7 +1472,6 @@ void SystemDictionaryShared::get_all_archived_classes(bool is_static_archive, Gr get_archive(is_static_archive)->_builtin_dictionary.iterate_all([&] (const RunTimeClassInfo* record) { classes->append(record->klass()); }); - get_archive(is_static_archive)->_unregistered_dictionary.iterate_all([&] (const RunTimeClassInfo* record) { classes->append(record->klass()); }); @@ -1506,8 +1511,7 @@ void SystemDictionaryShared::ArchiveInfo::print_table_statistics(const char* pre outputStream* st, bool is_static_archive) { st->print_cr("%sArchve Statistics", prefix); - _builtin_dictionary.print_table_statistics(st, "Builtin Shared Dictionary"); - _unregistered_dictionary.print_table_statistics(st, "Unregistered Shared Dictionary"); + _unregistered_dictionary.print_table_statistics(st, "AOT Incompatible Loaders Dictionary"); LambdaProxyClassDictionary::print_statistics(st, is_static_archive); } @@ -1540,7 +1544,7 @@ void SystemDictionaryShared::print_table_statistics(outputStream* st) { bool SystemDictionaryShared::is_dumptime_table_empty() { assert_lock_strong(DumpTimeTable_lock); _dumptime_table->update_counts(); - if (_dumptime_table->count_of(true) == 0 && _dumptime_table->count_of(false) == 0){ + if (_dumptime_table->count_of(true) == 0 && _dumptime_table->count_of(false) == 0) { return true; } return false; diff --git a/src/hotspot/share/classfile/systemDictionaryShared.hpp b/src/hotspot/share/classfile/systemDictionaryShared.hpp index 5c42e0fa96c..15b0716c642 100644 --- a/src/hotspot/share/classfile/systemDictionaryShared.hpp +++ b/src/hotspot/share/classfile/systemDictionaryShared.hpp @@ -170,8 +170,7 @@ class SystemDictionaryShared: public SystemDictionary { TRAPS); - static void write_dictionary(RunTimeSharedDictionary* dictionary, - bool is_builtin); + static void write_dictionary(RunTimeSharedDictionary* dictionary, bool is_builtin); static bool is_jfr_event_class(InstanceKlass *k); static void link_all_exclusion_check_candidates(InstanceKlass* ik); static bool should_be_excluded_impl(InstanceKlass* k, DumpTimeClassInfo* info); @@ -209,11 +208,11 @@ class SystemDictionaryShared: public SystemDictionary { static bool has_archived_enum_objs(InstanceKlass* ik); static void set_has_archived_enum_objs(InstanceKlass* ik); - static InstanceKlass* find_builtin_class(Symbol* class_name); + static InstanceKlass* find_builtin_class(Symbol* name); static const RunTimeClassInfo* find_record(RunTimeSharedDictionary* static_dict, RunTimeSharedDictionary* dynamic_dict, - Symbol* name); + Symbol* name, bool is_aot_unsafe_loader_class = false); static bool has_platform_or_app_classes(); diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index d9197444c1c..9fab8d52697 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -516,6 +516,10 @@ class SerializeClosure; template(objectWaiter_name, "objectWaiter") \ template(atKlassInit_name, "atKlassInit") \ template(hasArgsAtTop_name, "hasArgsAtTop") \ + template(getNamedPackage_name, "getNamedPackage") \ + template(getNamedPackage_signature, "(Ljava/lang/String;Ljava/lang/Module;)Ljava/lang/NamedPackage;") \ + template(getDefaultProtectionDomain_name, "getDefaultProtectionDomain") \ + template(getDefaultProtectionDomain_signature, "()Ljava/security/ProtectionDomain;") \ \ /* name symbols needed by intrinsics */ \ VM_INTRINSICS_DO(VM_INTRINSIC_IGNORE, VM_SYMBOL_IGNORE, template, VM_SYMBOL_IGNORE, VM_ALIAS_IGNORE) \ diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index c764a5ced58..ea6ee657e43 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -747,6 +747,11 @@ JVM_DesiredAssertionStatus(JNIEnv *env, jclass unused, jclass cls); JNIEXPORT jobject JNICALL JVM_AssertionStatusDirectives(JNIEnv *env, jclass unused); +JNIEXPORT jboolean JNICALL +JVM_RegisterAsAOTCompatibleLoader(JNIEnv *env, jobject loader); + +JNIEXPORT jboolean JNICALL +JVM_RegisterURLClassLoaderAsAOTSafeLoader(JNIEnv *env, jobject loader, jstring aot_id, jstring classpath); /* * java.lang.ref.Finalizer */ diff --git a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp index a348a78095a..9d82f1fab57 100644 --- a/src/hotspot/share/memory/universe.cpp +++ b/src/hotspot/share/memory/universe.cpp @@ -931,6 +931,9 @@ jint universe_init() { } #endif + // Create symbol table before initializing bootloader which needs symbol table for creating AOT identity + SymbolTable::create_table(); + ClassLoaderData::init_null_class_loader_data(); #if INCLUDE_CDS @@ -944,8 +947,6 @@ jint universe_init() { } #endif - SymbolTable::create_table(); - if (strlen(VerifySubSet) > 0) { Universe::initialize_verify_flags(); } diff --git a/src/hotspot/share/oops/constantPool.cpp b/src/hotspot/share/oops/constantPool.cpp index a00c9fb520b..98c12a40996 100644 --- a/src/hotspot/share/oops/constantPool.cpp +++ b/src/hotspot/share/oops/constantPool.cpp @@ -441,10 +441,11 @@ void ConstantPool::remove_unshareable_info() { } bool update_resolved_reference = true; +#if 0 if (CDSConfig::is_dumping_final_static_archive()) { ConstantPool* src_cp = ArchiveBuilder::current()->get_source_addr(this); InstanceKlass* src_holder = src_cp->pool_holder(); - if (src_holder->defined_by_other_loaders()) { + if (src_holder->defined_by_other_loaders() && src_holder->cl_aot_identity() == nullptr) { // Unregistered classes are not loaded in the AOT assembly phase. The resolved reference length // is already saved during the training run. precond(!src_holder->is_loaded()); @@ -453,7 +454,7 @@ void ConstantPool::remove_unshareable_info() { update_resolved_reference = false; } } - +#endif // resolved_references(): remember its length. If it cannot be restored // from the archived heap objects at run time, we need to dynamically allocate it. if (update_resolved_reference && cache() != nullptr) { diff --git a/src/hotspot/share/oops/cpCache.cpp b/src/hotspot/share/oops/cpCache.cpp index 75cdcb5310a..15cb650563f 100644 --- a/src/hotspot/share/oops/cpCache.cpp +++ b/src/hotspot/share/oops/cpCache.cpp @@ -541,8 +541,8 @@ void ConstantPoolCache::remove_resolved_indy_entries_if_non_deterministic() { bool ConstantPoolCache::can_archive_resolved_method(ConstantPool* src_cp, ResolvedMethodEntry* method_entry) { LogStreamHandle(Trace, aot, resolve) log; InstanceKlass* pool_holder = constant_pool()->pool_holder(); - if (pool_holder->defined_by_other_loaders()) { - // Archiving resolved cp entries for classes from non-builtin loaders + if (pool_holder->defined_by_other_loaders() && !pool_holder->is_defined_by_aot_safe_custom_loader()) { + // Archiving resolved cp entries for classes from non-builtin loaders or non-aot-safe custom loaders // is not yet supported. return false; } diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 9298d69f0b8..511aa0ea86e 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -4113,7 +4113,11 @@ void InstanceKlass::print_class_load_helper(ClassLoaderData* loader_data, info_stream.print(" loader:"); #if INCLUDE_CDS if (in_aot_cache()) { - info_stream.print(" %s", SystemDictionaryShared::loader_type_for_shared_class((Klass*)this)); + if (loader_data->aot_identity() != nullptr) { + info_stream.print(" %s", loader_data->aot_identity()->as_C_string()); + } else { + info_stream.print(" %s", SystemDictionaryShared::loader_type_for_shared_class((Klass*)this)); + } } else #endif if (loader_data == ClassLoaderData::the_null_class_loader_data()) { diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index c351d904420..84f3ecc7de5 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -327,6 +327,7 @@ class InstanceKlass: public Klass { bool defined_by_boot_loader() const { return _misc_flags.defined_by_boot_loader(); } bool defined_by_platform_loader() const { return _misc_flags.defined_by_platform_loader(); } bool defined_by_app_loader() const { return _misc_flags.defined_by_app_loader(); } + bool defined_by_builtin_loader() const { return _misc_flags.defined_by_builtin_loader(); } bool defined_by_other_loaders() const { return _misc_flags.defined_by_other_loaders(); } void set_class_loader_type() { _misc_flags.set_class_loader_type(_class_loader_data); } diff --git a/src/hotspot/share/oops/instanceKlassFlags.hpp b/src/hotspot/share/oops/instanceKlassFlags.hpp index d576805b30f..0880370daa6 100644 --- a/src/hotspot/share/oops/instanceKlassFlags.hpp +++ b/src/hotspot/share/oops/instanceKlassFlags.hpp @@ -102,6 +102,10 @@ class InstanceKlassFlags { IK_FLAGS_DO(IK_FLAGS_GET_SET) #undef IK_FLAGS_GET_SET + bool defined_by_builtin_loader() const { + return (_flags & builtin_loader_type_bits()) != 0; + } + bool defined_by_other_loaders() const { return (_flags & builtin_loader_type_bits()) == 0; } diff --git a/src/hotspot/share/oops/klass.cpp b/src/hotspot/share/oops/klass.cpp index 9a45958a347..35db505baa3 100644 --- a/src/hotspot/share/oops/klass.cpp +++ b/src/hotspot/share/oops/klass.cpp @@ -759,6 +759,7 @@ void Klass::metaspace_pointers_do(MetaspaceClosure* it) { } it->push(&_name); + it->push(&_cl_aot_identity); it->push(&_secondary_supers); for (int i = 0; i < _primary_super_limit; i++) { it->push(&_primary_supers[i]); diff --git a/src/hotspot/share/oops/klass.hpp b/src/hotspot/share/oops/klass.hpp index 6d6f41c23fc..e03b42a70bb 100644 --- a/src/hotspot/share/oops/klass.hpp +++ b/src/hotspot/share/oops/klass.hpp @@ -25,6 +25,8 @@ #ifndef SHARE_OOPS_KLASS_HPP #define SHARE_OOPS_KLASS_HPP +#include "cds/cdsConfig.hpp" +#include "classfile/classLoaderData.hpp" #include "oops/klassFlags.hpp" #include "oops/markWord.hpp" #include "oops/metadata.hpp" @@ -134,6 +136,7 @@ class Klass : public Metadata { // Class name. Instance classes: java/lang/String, etc. Array classes: [I, // [Ljava/lang/String;, etc. Set to zero for all other kinds of classes. Symbol* _name; + Symbol* _cl_aot_identity; // Cache of last observed secondary supertype Klass* _secondary_super_cache; @@ -309,7 +312,13 @@ class Klass : public Metadata { // class loader data ClassLoaderData* class_loader_data() const { return _class_loader_data; } - void set_class_loader_data(ClassLoaderData* loader_data) { _class_loader_data = loader_data; } + void set_class_loader_data(ClassLoaderData* loader_data) { + _class_loader_data = loader_data; + } + + Symbol* cl_aot_identity() const { return _cl_aot_identity; } + void set_cl_aot_identity(Symbol* aot_id) { _cl_aot_identity = aot_id; } + bool is_defined_by_aot_safe_custom_loader() const { return _cl_aot_identity != nullptr; } s2 shared_classpath_index() const { return _shared_class_path_index; diff --git a/src/hotspot/share/oops/symbol.cpp b/src/hotspot/share/oops/symbol.cpp index 65a5015350c..80e15440bbf 100644 --- a/src/hotspot/share/oops/symbol.cpp +++ b/src/hotspot/share/oops/symbol.cpp @@ -417,3 +417,13 @@ bool Symbol::is_valid_id(vmSymbolID vm_symbol_id) { return vmSymbols::is_valid_id(vm_symbol_id); } #endif + +unsigned Symbol::symbol_hash(Symbol* const& sym) { + ResourceMark rm; + char* str = sym->as_C_string(); + return java_lang_String::hash_code(str, strlen(str)); +} + +bool Symbol::symbol_equals(Symbol* const& sym1, Symbol* const& sym2) { + return sym1->equals(sym2); +} diff --git a/src/hotspot/share/oops/symbol.hpp b/src/hotspot/share/oops/symbol.hpp index b73e2ff46cf..353dbe5ab53 100644 --- a/src/hotspot/share/oops/symbol.hpp +++ b/src/hotspot/share/oops/symbol.hpp @@ -197,6 +197,10 @@ class Symbol : public MetaspaceObj { int utf8_length() const { return length(); } + bool equals(const Symbol* sym) { + return equals((const char*)sym->bytes(), sym->utf8_length()); + } + // Compares the symbol with a string. bool equals(const char* str, int len) const { int l = utf8_length(); @@ -325,6 +329,9 @@ class Symbol : public MetaspaceObj { static size_t _total_count; #endif + + static unsigned symbol_hash(Symbol* const& sym); + static bool symbol_equals(Symbol* const& sym1, Symbol* const& sym2); }; // Note: this comparison is used for vtable sorting only; it doesn't matter diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index ed87366419a..948f28c50da 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -23,12 +23,15 @@ */ #include "cds/aotClassInitializer.hpp" +#include "cds/aotClassLocation.hpp" #include "cds/aotConstantPoolResolver.hpp" +#include "cds/aotLinkedClassBulkLoader.hpp" #include "cds/aotMetaspace.hpp" #include "cds/cdsConfig.hpp" #include "cds/classListParser.hpp" #include "cds/classListWriter.hpp" #include "cds/dynamicArchive.hpp" +#include "cds/finalImageRecipes.hpp" #include "cds/heapShared.hpp" #include "cds/lambdaFormInvokers.hpp" #include "cds/lambdaProxyClassDictionary.hpp" @@ -2241,6 +2244,43 @@ JVM_ENTRY_PROF(jobject, JVM_AssertionStatusDirectives, JVM_AssertionStatusDirect return JNIHandles::make_local(THREAD, asd); JVM_END +JVM_ENTRY_PROF(jboolean, JVM_RegisterAsAOTCompatibleLoader, JVM_RegisterAsAOTCompatibleLoader(JNIEnv *env, jobject loader)) + if (CDSConfig::is_using_aot_linked_classes() && CDSConfig::supports_custom_loaders()) { + Handle h_loader(THREAD, JNIHandles::resolve_non_null(loader)); + ClassLoaderData *loader_data = SystemDictionary::register_loader(h_loader); + AOTLinkedClassBulkLoader::preload_classes_for_loader(loader_data, CHECK_AND_CLEAR_(JNI_FALSE)); + AOTLinkedClassBulkLoader::link_classes_for_loader(loader_data, CHECK_AND_CLEAR_(JNI_FALSE)); + return JNI_TRUE; + } + return JNI_FALSE; +JVM_END + +JVM_ENTRY_PROF(jboolean, JVM_RegisterURLClassLoaderAsAOTSafeLoader, JVM_RegisterURLClassLoaderAsAOTSafeLoader(JNIEnv *env, jobject loader, jstring aot_id, jstring classpath)) + if (CDSConfig::supports_custom_loaders()) { + ResourceMark rm(THREAD); + Handle h_loader(THREAD, JNIHandles::resolve_non_null(loader)); + ClassLoaderData *loader_data = SystemDictionary::register_loader(h_loader); + assert(loader_data->aot_identity() == nullptr, "loader's aot identity should not be set"); + const char* aot_id_str = java_lang_String::as_utf8_string(JNIHandles::resolve_non_null(aot_id)); + Symbol* aot_id_symbol = SymbolTable::new_symbol(aot_id_str); + const char *classpath_str = java_lang_String::as_utf8_string(JNIHandles::resolve_non_null(classpath)); + if (CDSConfig::is_dumping_preimage_static_archive()) { + if (!URLClassLoaderClasspathSupport::add_urlclassloader_classpath(loader_data, aot_id_symbol, classpath_str)) { + return JNI_FALSE; + } + loader_data->set_aot_identity(aot_id_symbol); + } else if (CDSConfig::is_using_aot_linked_classes()) { + if (!URLClassLoaderClasspathSupport::claim_and_verify_archived_classpath(loader_data, aot_id_symbol, classpath_str)) { + return JNI_FALSE; + } + loader_data->set_aot_identity(aot_id_symbol); + AOTLinkedClassBulkLoader::preload_classes_for_loader(loader_data, CHECK_AND_CLEAR_(JNI_FALSE)); + AOTLinkedClassBulkLoader::link_classes_for_loader(loader_data, CHECK_AND_CLEAR_(JNI_FALSE)); + } + return JNI_TRUE; + } + return JNI_FALSE; +JVM_END // Verification //////////////////////////////////////////////////////////////////////////////// // Reflection for the verifier ///////////////////////////////////////////////////////////////// diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index 684c8e239c8..0d5616dd220 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -2219,6 +2219,38 @@ WB_ENTRY(jboolean, WB_IsSharedClass(JNIEnv* env, jobject wb, jclass clazz)) return (jboolean)AOTMetaspace::in_aot_cache(java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz))); WB_END +WB_ENTRY(jboolean, WB_IsLoadedByBuiltinLoader(JNIEnv* env, jobject wb, jclass clazz)) + Klass* klass = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)); + assert(klass->is_instance_klass(), "must be InstanceKlass"); + return InstanceKlass::cast(klass)->defined_by_builtin_loader(); +WB_END + +WB_ENTRY(jboolean, WB_IsLoadedByAOTSafeCustomLoader(JNIEnv* env, jobject wb, jclass clazz)) + Klass* klass = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)); + assert(klass->is_instance_klass(), "must be InstanceKlass"); + return InstanceKlass::cast(klass)->is_defined_by_aot_safe_custom_loader(); +WB_END + +WB_ENTRY(jboolean, WB_IsLoadedByAOTUnsafeCustomLoader(JNIEnv* env, jobject wb, jclass clazz)) + Klass* klass = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)); + assert(klass->is_instance_klass(), "must be InstanceKlass"); + InstanceKlass* ik = InstanceKlass::cast(klass); + return !ik->defined_by_builtin_loader() && !ik->is_defined_by_aot_safe_custom_loader(); +WB_END + +WB_ENTRY(jboolean, WB_IsAOTSafeCustomLoader(JNIEnv* env, jobject wb, jobject loader)) + oop class_loader_oop = JNIHandles::resolve(loader); + if (SystemDictionary::is_builtin_class_loader(class_loader_oop)) { + return false; + } + ClassLoaderData* cld = java_lang_ClassLoader::loader_data_acquire(class_loader_oop); + return cld->aot_identity() != nullptr; +WB_END + +WB_ENTRY(jboolean, WB_AreSharedStringsMapped(JNIEnv* env)) + return AOTMappedHeapLoader::is_mapped(); +WB_END + WB_ENTRY(void, WB_LinkClass(JNIEnv* env, jobject wb, jclass clazz)) Klass *k = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)); if (!k->is_instance_klass()) { @@ -3044,6 +3076,11 @@ static JNINativeMethod methods[] = { {CC"getCurrentCDSVersion", CC"()I", (void*)&WB_GetCDSCurrentVersion}, {CC"isSharingEnabled", CC"()Z", (void*)&WB_IsSharingEnabled}, {CC"isSharedClass", CC"(Ljava/lang/Class;)Z", (void*)&WB_IsSharedClass }, + {CC"isLoadedByBuiltinLoader", CC"(Ljava/lang/Class;)Z", (void*)&WB_IsLoadedByBuiltinLoader}, + {CC"isLoadedByAOTSafeCustomLoader", CC"(Ljava/lang/Class;)Z", (void*)&WB_IsLoadedByAOTSafeCustomLoader}, + {CC"isLoadedByAOTUnsafeCustomLoader", CC"(Ljava/lang/Class;)Z", (void*)&WB_IsLoadedByAOTUnsafeCustomLoader}, + {CC"isAOTSafeCustomLoader", CC"(Ljava/lang/ClassLoader;)Z", (void*)&WB_IsAOTSafeCustomLoader}, + {CC"areSharedStringsMapped", CC"()Z", (void*)&WB_AreSharedStringsMapped }, {CC"linkClass", CC"(Ljava/lang/Class;)V", (void*)&WB_LinkClass}, {CC"areOpenArchiveHeapObjectsMapped", CC"()Z", (void*)&WB_AreOpenArchiveHeapObjectsMapped}, {CC"isCDSIncluded", CC"()Z", (void*)&WB_IsCDSIncluded }, diff --git a/src/hotspot/share/runtime/mutexLocker.cpp b/src/hotspot/share/runtime/mutexLocker.cpp index 20e23903938..e32e9d609a0 100644 --- a/src/hotspot/share/runtime/mutexLocker.cpp +++ b/src/hotspot/share/runtime/mutexLocker.cpp @@ -163,6 +163,7 @@ Mutex* UnregisteredClassesTable_lock= nullptr; Mutex* LambdaFormInvokers_lock = nullptr; Mutex* ScratchObjects_lock = nullptr; Mutex* FinalImageRecipes_lock = nullptr; +Mutex* URLClassLoaderClasspath_lock = nullptr; #endif // INCLUDE_CDS Mutex* Bootclasspath_lock = nullptr; @@ -326,6 +327,7 @@ void mutex_init() { MUTEX_DEFN(LambdaFormInvokers_lock , PaddedMutex , safepoint); MUTEX_DEFL(ScratchObjects_lock , PaddedMutex , DumpTimeTable_lock); MUTEX_DEFN(FinalImageRecipes_lock , PaddedMutex , nosafepoint); + MUTEX_DEFN(URLClassLoaderClasspath_lock , PaddedMutex , nosafepoint); #endif // INCLUDE_CDS MUTEX_DEFN(Bootclasspath_lock , PaddedMutex , nosafepoint); diff --git a/src/hotspot/share/runtime/mutexLocker.hpp b/src/hotspot/share/runtime/mutexLocker.hpp index 3cf66dbc869..49523aaa3f5 100644 --- a/src/hotspot/share/runtime/mutexLocker.hpp +++ b/src/hotspot/share/runtime/mutexLocker.hpp @@ -141,6 +141,7 @@ extern Mutex* UnregisteredClassesTable_lock; // UnregisteredClassesTableTabl extern Mutex* LambdaFormInvokers_lock; // Protecting LambdaFormInvokers::_lambdaform_lines extern Mutex* ScratchObjects_lock; // Protecting _scratch_xxx_table in heapShared.cpp extern Mutex* FinalImageRecipes_lock; // Protecting the tables used by FinalImageRecipes. +extern Mutex* URLClassLoaderClasspath_lock; #endif // INCLUDE_CDS #if INCLUDE_JFR extern Mutex* JfrStacktrace_lock; // used to guard access to the JFR stacktrace table diff --git a/src/java.base/share/classes/java/lang/ClassLoader.java b/src/java.base/share/classes/java/lang/ClassLoader.java index fdb5a80e2a2..3a0ec4b8b4b 100644 --- a/src/java.base/share/classes/java/lang/ClassLoader.java +++ b/src/java.base/share/classes/java/lang/ClassLoader.java @@ -337,8 +337,14 @@ void addClass(Class c) { private final ConcurrentHashMap packages = new ConcurrentHashMap<>(); - /* + /** * Returns a named package for the given module. + * + * @param pn + * package name + * @param m + * module + * @return NamedPackage */ private NamedPackage getNamedPackage(String pn, Module m) { NamedPackage p = packages.get(pn); @@ -2650,6 +2656,41 @@ private void resetArchivedStates() { classes.trimToSize(); classLoaderValueMap = null; } + + private String aotIdentity = null; + + /** + * Set unique and repeatable ID of the classloader + * + * @param id + * unique id of the classloader object. + */ + public void setAOTIdentity(String id) { + aotIdentity = id; + //registerAsAOTCompatibleLoader(); + } + + /** + * Returns unique and repeatable ID of the classloader + * + * @return unique id of the classloader object. + */ + public String getAOTIdentity() { + return aotIdentity; + } + + /** + * Returns true if the parent is a builtin loader + * + * @return true if the parent is a builtin loader + */ + @SuppressWarnings("this-escape") + public final boolean hasBuiltinLoaderAsParent() { + ClassLoader parent = getParent(); + return parent == null || parent == getBuiltinPlatformClassLoader() || parent == getBuiltinAppClassLoader(); + } + + private native boolean registerAsAOTCompatibleLoader(); } /* diff --git a/src/java.base/share/classes/java/net/URLClassLoader.java b/src/java.base/share/classes/java/net/URLClassLoader.java index 4b579554e0a..6edbeb14959 100644 --- a/src/java.base/share/classes/java/net/URLClassLoader.java +++ b/src/java.base/share/classes/java/net/URLClassLoader.java @@ -26,6 +26,7 @@ package java.net; import java.io.Closeable; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.security.CodeSigner; @@ -64,10 +65,11 @@ * @author David Connelly * @since 1.2 */ +@SuppressWarnings("this-escape") public class URLClassLoader extends SecureClassLoader implements Closeable { /* The search path for classes and resources */ private final URLClassPath ucp; - + private static final boolean DEBUG = Boolean.getBoolean("urlclassloader.debug"); /** * Constructs a new URLClassLoader for the given URLs. The URLs will be * searched in the order specified for classes and resources after first @@ -92,6 +94,7 @@ public class URLClassLoader extends SecureClassLoader implements Closeable { public URLClassLoader(URL[] urls, ClassLoader parent) { super(parent); this.ucp = new URLClassPath(urls); + registerAsAOTSafe(urls); } /** @@ -111,6 +114,7 @@ public URLClassLoader(URL[] urls, ClassLoader parent) { public URLClassLoader(URL[] urls) { super(); this.ucp = new URLClassPath(urls); + registerAsAOTSafe(urls); } /** @@ -138,6 +142,7 @@ public URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) { super(parent); this.ucp = new URLClassPath(urls, factory); + registerAsAOTSafe(urls); } @@ -171,6 +176,7 @@ public URLClassLoader(String name, ClassLoader parent) { super(name, parent); this.ucp = new URLClassPath(urls); + registerAsAOTSafe(urls); } /** @@ -202,6 +208,7 @@ public URLClassLoader(String name, URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) { super(name, parent); this.ucp = new URLClassPath(urls, factory); + registerAsAOTSafe(urls); } /* A map (used as a set) to keep track of closeable local resources @@ -639,4 +646,69 @@ public static URLClassLoader newInstance(final URL[] urls) { static { ClassLoader.registerAsParallelCapable(); } + + // It is AOT-safe if only jar files are present in it the urls + private boolean canRegisterAsAOTSafe(final URL[] urls) { + boolean isAOTSafe = true; + for (URL url: urls) { + if (!url.getProtocol().equals("file")) { + isAOTSafe = false; + break; + } + if (!url.getPath().endsWith(".jar")) { + isAOTSafe = false; + break; + } + } + return isAOTSafe; + } + + private String createClassPath(final URL[] urls) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < urls.length; i++) { + URL url = urls[i]; + String path = url.getPath(); + sb.append(path); + if (i < urls.length-1) { + sb.append(File.pathSeparator); + } + } + return sb.toString(); + } + + private String convertToAOTId(String classpath) { + return classpath; + } + + private boolean registerAsAOTSafe(final URL[] urls) { + String classpath = createClassPath(urls); + if (!hasBuiltinLoaderAsParent()) { + if (DEBUG) { + System.out.println("DEBUG: URLClassLoader with classpath \"" + classpath + "\" cannot be registered as AOT-safe (reason=parent is not built-in loader)"); + } + return false; + } + if (canRegisterAsAOTSafe(urls)) { + String aotId = convertToAOTId(classpath); + boolean rc = registerAsAOTSafeImpl(aotId, classpath); + if (rc) { + setAOTIdentity(aotId); + if (DEBUG) { + System.out.println("DEBUG: Registered URLClassLoader as AOT-safe with classpath " + classpath); + } + } else { + if (DEBUG) { + System.out.println("DEBUG: Failed to register URLClassLoader as AOT-safe with classpath " + classpath); + } + } + return rc; + } else { + if (DEBUG) { + System.out.println("DEBUG: URLClassLoader with classpath \"" + classpath + "\" cannot be registered as AOT-safe (reason=urls contain non-jar files)"); + } + } + return false; + } + + private native boolean registerAsAOTSafeImpl(String aotId, String classpath); } diff --git a/src/java.base/share/native/libjava/ClassLoader.c b/src/java.base/share/native/libjava/ClassLoader.c index 4519a4777f8..4c272559edf 100644 --- a/src/java.base/share/native/libjava/ClassLoader.c +++ b/src/java.base/share/native/libjava/ClassLoader.c @@ -35,7 +35,8 @@ #include "jvm.h" static JNINativeMethod methods[] = { - {"retrieveDirectives", "()Ljava/lang/AssertionStatusDirectives;", (void *)&JVM_AssertionStatusDirectives} + {"retrieveDirectives", "()Ljava/lang/AssertionStatusDirectives;", (void *)&JVM_AssertionStatusDirectives}, + {"registerAsAOTCompatibleLoader", "()Z", (void *)&JVM_RegisterAsAOTCompatibleLoader} }; JNIEXPORT void JNICALL diff --git a/src/java.base/share/native/libjava/URLClassLoader.c b/src/java.base/share/native/libjava/URLClassLoader.c new file mode 100644 index 00000000000..7c8e87928a0 --- /dev/null +++ b/src/java.base/share/native/libjava/URLClassLoader.c @@ -0,0 +1,10 @@ + +#include "jni.h" +#include "jni_util.h" +#include "jvm.h" + +JNIEXPORT jboolean JNICALL +Java_java_net_URLClassLoader_registerAsAOTSafeImpl(JNIEnv *env, jobject loader, jstring aot_id, jstring classpath) +{ + return JVM_RegisterURLClassLoaderAsAOTSafeLoader(env, loader, aot_id, classpath); +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/MultiLevelDelegationTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/MultiLevelDelegationTest.java new file mode 100644 index 00000000000..b6258054117 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/MultiLevelDelegationTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2026, 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 Test multi-level delegation with URLClassLoader L1 delegates to system loader + * and another URLClassLoader L2 delegating to L1 (L2 --> L1 --> SL). + * Classes loaded by L1 should get full AOTCache support but + * classes loaded by L2 would be added to "unregistered" category. + * Only those URLClassLoaders with builtin loader as the parent are currently fully + * supported in AOTCache. + * + * @requires vm.cds + * @requires vm.cds.custom.loaders + * + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds + * @compile test-classes/CustomLoadee.java + * test-classes/CustomLoadee3.java + * test-classes/MultiLevelDelegation.java + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar WhiteBox.jar jdk.test.whitebox.WhiteBox + * @run driver MultiLevelDelegationTest + */ + +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.whitebox.WhiteBox; + +public class MultiLevelDelegationTest { + private static String appJar; + private static String customJar; + private static String customJar2; + private static final String mainClass = "MultiLevelDelegation"; + + public static void main(String[] args) throws Exception { + appJar = JarBuilder.build("MultiLevelDelegationTest", "MultiLevelDelegation"); + // jar file for loader L1 + customJar = JarBuilder.build("MultiLevelDelegationTest_customjar", "CustomLoadee"); + // jar file for loader L2 + customJar2 = JarBuilder.build("MultiLevelDelegationTest_customjar2", "CustomLoadee3"); + + Tester tester = new Tester(); + tester.runAOTWorkflow("AOT", "--two-step-training"); + } + + static class Tester extends CDSAppTester { + public Tester() { + super(mainClass); + useWhiteBox(ClassFileInstaller.getJarPath("WhiteBox.jar")); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] vmArgs(RunMode runMode) { + return new String[] { + "-Xlog:aot+load", + "-XX:+AOTCacheSupportForCustomLoader" + }; + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { + mainClass, + customJar, + customJar2, + }; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { + if (isAOTWorkflow()) { + if (runMode == RunMode.TRAINING) { + // L1's class (CustomLoadee from customJar) should be in a custom loader category + out.shouldMatch("category .*MultiLevelDelegationTest_customjar.jar\\[0\\] CustomLoadee"); + // L2's class (CustomLoadee3 from customJar2) should be in "unregistered" category + // because its parent is not the system class loader + out.shouldMatch("category unreg\\[0\\] CustomLoadee3"); + } + if (runMode == RunMode.PRODUCTION) { + // L1's class should be eagerly loaded from the AOT cache + out.shouldMatch("MultiLevelDelegationTest_customjar.jar CustomLoadee"); + // L2's class should NOT be eagerly loaded from the AOT cache + out.shouldNotMatch("MultiLevelDelegationTest_customjar2.jar CustomLoadee3"); + } + } + } + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/MultipleURLClassLoadersWithSameClasspathTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/MultipleURLClassLoadersWithSameClasspathTest.java new file mode 100644 index 00000000000..fcbf71b15ed --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/MultipleURLClassLoadersWithSameClasspathTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2026, 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 Test for multiple instances of URLClassLoader with same classpath. + * + * @requires vm.cds + * @requires vm.cds.custom.loaders + * + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds + * @compile test-classes/CustomLoadee.java + * test-classes/CustomLoadee3.java + * test-classes/CustomLoadee5.java + * test-classes/MultipleURLClassLoadersSameClasspath.java + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar WhiteBox.jar jdk.test.whitebox.WhiteBox + * @run driver MultipleURLClassLoadersWithSameClasspathTest + */ + +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.whitebox.WhiteBox; + + +public class MultipleURLClassLoadersWithSameClasspathTest { + private static String appJar; + private static String customJar; + private static final String mainClass = "MultipleURLClassLoadersSameClasspath"; + + public static void main(String[] args) throws Exception { + appJar = JarBuilder.build("MultipleURLClassLoadersWithSameClasspathTest", "MultipleURLClassLoadersSameClasspath"); + + customJar = JarBuilder.build("MultipleURLClassLoadersWithSameClasspathTest_customjar", "CustomLoadee", "CustomLoadee3"); + + Tester tester = new Tester(); + tester.runAOTWorkflow("AOT", "--two-step-training"); + } + + static class Tester extends CDSAppTester { + public Tester() { + super(mainClass); + useWhiteBox(ClassFileInstaller.getJarPath("WhiteBox.jar")); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] vmArgs(RunMode runMode) { + return new String[] { + "-Xlog:aot+load", + "-XX:+AOTCacheSupportForCustomLoader", + }; + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { + mainClass, + customJar + }; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { + if (isAOTWorkflow()) { + if (runMode == RunMode.TRAINING) { + out.shouldMatch("category .*MultipleURLClassLoadersWithSameClasspathTest_customjar.jar\\[0\\] CustomLoadee"); + } + if (runMode == RunMode.PRODUCTION) { + out.shouldMatch("MultipleURLClassLoadersWithSameClasspathTest_customjar.jar CustomLoadee"); + } + } + } + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/SameNameInTwoLoadersTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/SameNameInTwoLoadersTest.java new file mode 100644 index 00000000000..da261423adc --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/SameNameInTwoLoadersTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2026, 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 Testing the loading of a class with the same name in two different instances of URLClassLoader + * with partially overlapping classpaths. The test is run with support for URLClassLoader enabled + * in AOTCache. + * + * @requires vm.cds + * @requires vm.cds.custom.loaders + * + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds + * @compile test-classes/CustomLoadee.java + * test-classes/CustomLoadee3.java + * test-classes/CustomLoadee5.java + * test-classes/SameNameUnrelatedLoaders.java + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar WhiteBox.jar jdk.test.whitebox.WhiteBox + * @run driver SameNameInTwoLoadersTest + */ + +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.whitebox.WhiteBox; + + +public class SameNameInTwoLoadersTest { + private static String appJar; + private static String commonJar; + private static String customJar1; + private static String customJar2; + private static final String mainClass = "SameNameUnrelatedLoaders"; + + public static void main(String[] args) throws Exception { + appJar = JarBuilder.build("SameNameInTwoLoadersTest", "SameNameUnrelatedLoaders"); + + commonJar = JarBuilder.build("SameNameInTwoLoadersTest_commonjar", "CustomLoadee"); + customJar1 = JarBuilder.build("SameNameInTwoLoadersTest_customjar1", "CustomLoadee3"); + customJar2 = JarBuilder.build("SameNameInTwoLoadersTest_customjar2", "CustomLoadee5"); + + Tester tester = new Tester(); + tester.runAOTWorkflow("AOT", "--two-step-training"); + } + + static class Tester extends CDSAppTester { + public Tester() { + super(mainClass); + useWhiteBox(ClassFileInstaller.getJarPath("WhiteBox.jar")); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] vmArgs(RunMode runMode) { + return new String[] { + "-Xlog:aot+load", + "-XX:+AOTCacheSupportForCustomLoader", + }; + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { + mainClass, + commonJar, + customJar1, + customJar2 + }; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { + if (isAOTWorkflow()) { + if (runMode == RunMode.TRAINING) { + out.shouldMatch("category .*SameNameInTwoLoadersTest_customjar1.jar\\[0\\] CustomLoadee"); + out.shouldMatch("category .*SameNameInTwoLoadersTest_customjar2.jar\\[0\\] CustomLoadee"); + } + if (runMode == RunMode.PRODUCTION) { + out.shouldMatch("SameNameInTwoLoadersTest_customjar1.jar CustomLoadee"); + out.shouldMatch("SameNameInTwoLoadersTest_customjar2.jar CustomLoadee"); + } + } + } + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/SingleURLClassLoaderTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/SingleURLClassLoaderTest.java new file mode 100644 index 00000000000..0cccebb67e9 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/SingleURLClassLoaderTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2026, 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 Testing the loading of a class with an instances of URLClassLoader. + * URLClassLoader instance has system loader as its parent, and shares its classpath with the parent. + * The test is run with support for URLClassLoader enabled in AOTCache. + * It verifies the class is stored in AOTCache and loaded from the AOTCache in production run. + * + * @requires vm.cds + * @requires vm.cds.custom.loaders + * + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds + * @compile test-classes/CustomLoadee.java + * test-classes/CustomLoadee3.java + * test-classes/CustomLoadee5.java + * test-classes/SingleURLClassLoader.java + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar WhiteBox.jar jdk.test.whitebox.WhiteBox + * @run driver SingleURLClassLoaderTest + */ + +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.whitebox.WhiteBox; + +public class SingleURLClassLoaderTest { + private static String appJar; + private static String customJar; + private static final String mainClass = "SingleURLClassLoader"; + + public static void main(String[] args) throws Exception { + appJar = JarBuilder.build("SingleURLClassLoaderTest", "SingleURLClassLoader", "CustomLoadee"); + + customJar = JarBuilder.build("SingleURLClassLoaderTest_customjar", "CustomLoadee3", "CustomLoadee5"); + + Tester tester = new Tester(); + tester.runAOTWorkflow("AOT", "--two-step-training"); + } + + static class Tester extends CDSAppTester { + public Tester() { + super(mainClass); + useWhiteBox(ClassFileInstaller.getJarPath("WhiteBox.jar")); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] vmArgs(RunMode runMode) { + return new String[] { + "-Xlog:aot+load", + "-XX:+AOTCacheSupportForCustomLoader", + }; + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { + mainClass, + appJar, + customJar, + }; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { + if (isAOTWorkflow()) { + if (runMode == RunMode.TRAINING) { + out.shouldMatch("category .*SingleURLClassLoaderTest_customjar.jar\\[0\\] CustomLoadee3"); + } + if (runMode == RunMode.PRODUCTION) { + out.shouldMatch("SingleURLClassLoaderTest_customjar.jar CustomLoadee3"); + } + } + } + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/CustomLoadee.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/CustomLoadee.java new file mode 100644 index 00000000000..5cc1e412c8d --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/CustomLoadee.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2015, 2019, 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. + * + */ + +public class CustomLoadee { + public String toString() { + return "this is CustomLoadee"; + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/CustomLoadee3.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/CustomLoadee3.java new file mode 100644 index 00000000000..71e8fe2a8c4 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/CustomLoadee3.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2015, 2019, 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. + * + */ + +public class CustomLoadee3 { + public String toString() { + return "this is CustomLoadee3"; + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/CustomLoadee5.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/CustomLoadee5.java new file mode 100644 index 00000000000..ba9f6dafa95 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/CustomLoadee5.java @@ -0,0 +1,28 @@ +/* + * 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. + */ + +public class CustomLoadee5 { + public String toString() { + return "this is CustomLoadee5"; + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/MultiLevelDelegation.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/MultiLevelDelegation.java new file mode 100644 index 00000000000..e9b1784a579 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/MultiLevelDelegation.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2026, 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. + * + */ + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import jdk.test.whitebox.WhiteBox; + +public class MultiLevelDelegation { + static URLClassLoader loader1; + static URLClassLoader loader2; + + public static void main(String args[]) throws Exception { + if (args.length < 2) { + throw new RuntimeException("insufficient arguments"); + } + URL url1 = new File(args[0]).toURI().toURL(); + URL url2 = new File(args[1]).toURI().toURL(); + + // loader1 has system loader as its parent + loader1 = new URLClassLoader(new URL[] {url1}); + + // loader2 has loader1 has its parent + loader2 = new URLClassLoader(new URL[] {url2}, loader1); + + Class cls1 = loader1.loadClass("CustomLoadee"); + System.out.println("CustomLoadee loaded by: " + cls1.getClassLoader()); + + if (cls1.getClassLoader() != loader1) { + throw new RuntimeException("CustomLoadee should be loaded by loader1"); + } + + Class cls2 = loader2.loadClass("CustomLoadee3"); + System.out.println("CustomLoadee3 loaded by: " + cls2.getClassLoader()); + + if (cls2.getClassLoader() != loader2) { + throw new RuntimeException("CustomLoadee3 should be loaded by loader2"); + } + + WhiteBox wb = WhiteBox.getWhiteBox(); + + if (!wb.isAOTSafeCustomLoader(loader1)) { + throw new RuntimeException("loader1 should be marked as aot-safe"); + } + if (wb.isAOTSafeCustomLoader(loader2)) { + throw new RuntimeException("loader2 should not be marked as aot-safe"); + } + + if (wb.isSharedClass(MultiLevelDelegation.class)) { + if (!wb.isSharedClass(cls1)) { + throw new RuntimeException("CustomLoadee (loaded by loader1) should be shared"); + } + if (!wb.isSharedClass(cls2)) { + throw new RuntimeException("CustomLoadee3 (loaded by loader2) should be shared"); + } + if (!wb.isLoadedByAOTSafeCustomLoader(cls1)) { + throw new RuntimeException("CustomLoadee should have been loaded by AOT-safe loader"); + } + if (wb.isLoadedByAOTSafeCustomLoader(cls2)) { + throw new RuntimeException("CustomLoadee3 should not have been loaded by AOT-safe loader"); + } + } + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/MultipleURLClassLoadersSameClasspath.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/MultipleURLClassLoadersSameClasspath.java new file mode 100644 index 00000000000..32f1a3e685a --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/MultipleURLClassLoadersSameClasspath.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2026, 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. + * + */ + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import jdk.test.whitebox.WhiteBox; + +public class MultipleURLClassLoadersSameClasspath { + static URLClassLoader loader1, loader2, loader3; + + public static void main(String args[]) throws Exception { + if (args.length < 1) { + throw new RuntimeException("insufficient arguments"); + } + String path = args[0]; + URL url = new File(path).toURI().toURL(); + URL[] urls = new URL[] {url}; + + loader1 = new URLClassLoader(urls); + loader2 = new URLClassLoader(urls); + loader3 = new URLClassLoader(urls); + + Class class01 = loader1.loadClass("CustomLoadee"); + Class class02 = loader2.loadClass("CustomLoadee"); + Class class03 = loader3.loadClass("CustomLoadee"); + + System.out.println("class01 = " + class01); + System.out.println("class02 = " + class02); + System.out.println("class03 = " + class03); + + if (class01.getClassLoader() != loader1) { + throw new RuntimeException("class01 loaded by wrong loader"); + } + if (class02.getClassLoader() != loader2) { + throw new RuntimeException("class02 loaded by wrong loader"); + } + if (class03.getClassLoader() != loader3) { + throw new RuntimeException("class03 loaded by wrong loader"); + } + + WhiteBox wb = WhiteBox.getWhiteBox(); + + if (!wb.isAOTSafeCustomLoader(loader1)) { + throw new RuntimeException("loader1 should be marked as aot-safe"); + } + if (wb.isAOTSafeCustomLoader(loader2)) { + throw new RuntimeException("loader2 should not be marked as aot-safe"); + } + if (wb.isAOTSafeCustomLoader(loader3)) { + throw new RuntimeException("loader3 should not be marked as aot-safe"); + } + + if (wb.isSharedClass(MultipleURLClassLoadersSameClasspath.class)) { + if (!wb.isSharedClass(class01)) { + throw new RuntimeException("first class is not shared"); + } + if (!wb.isSharedClass(class02)) { + throw new RuntimeException("second class is not shared"); + } + if (wb.isSharedClass(class03)) { + throw new RuntimeException("third class should not be shared"); + } + if (!wb.isLoadedByAOTSafeCustomLoader(class01)) { + throw new RuntimeException("first class should have been loaded by AOT-safe loader"); + } + if (wb.isLoadedByAOTSafeCustomLoader(class02)) { + throw new RuntimeException("second class should not have been loaded by AOT-safe loader"); + } + } + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/SameNameUnrelatedLoaders.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/SameNameUnrelatedLoaders.java new file mode 100644 index 00000000000..74d29d011fa --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/SameNameUnrelatedLoaders.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2026, 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. + * + */ + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import jdk.test.whitebox.WhiteBox; + +public class SameNameUnrelatedLoaders { + static URLClassLoader loader1, loader2; + + public static void main(String args[]) throws Exception { + if (args.length < 3) { + throw new RuntimeException("insufficient arguments"); + } + URL commonJarUrl = new File(args[0]).toURI().toURL(); + URL jar1Url = new File(args[1]).toURI().toURL(); + URL jar2Url = new File(args[2]).toURI().toURL(); + + loader1 = new URLClassLoader(new URL[]{commonJarUrl, jar1Url}); + loader2 = new URLClassLoader(new URL[]{commonJarUrl, jar2Url}); + + Class class01 = loader1.loadClass("CustomLoadee"); + Class class02 = loader2.loadClass("CustomLoadee"); + + System.out.println("class01 = " + class01); + System.out.println("class02 = " + class02); + + if (class01.getClassLoader() != loader1) { + throw new RuntimeException("class01 loaded by wrong loader"); + } + if (class02.getClassLoader() != loader2) { + throw new RuntimeException("class02 loaded by wrong loader"); + } + + if (true) { + if (class01.isAssignableFrom(class02)) { + throw new RuntimeException("assignable condition failed"); + } + + Object obj01 = class01.newInstance(); + Object obj02 = class02.newInstance(); + + if (class01.isInstance(obj02)) { + throw new RuntimeException("instance relationship condition 01 failed"); + } + if (class02.isInstance(obj01)) { + throw new RuntimeException("instance relationship condition 02 failed"); + } + } + + WhiteBox wb = WhiteBox.getWhiteBox(); + + if (!wb.isAOTSafeCustomLoader(loader1)) { + throw new RuntimeException("loader1 should be marked as aot-safe"); + } + if (!wb.isAOTSafeCustomLoader(loader2)) { + throw new RuntimeException("loader2 should be marked as aot-safe"); + } + + if (wb.isSharedClass(SameNameUnrelatedLoaders.class)) { + if (!wb.isSharedClass(class01)) { + throw new RuntimeException("first class is not shared"); + } + if (!wb.isSharedClass(class02)) { + throw new RuntimeException("second class is not shared"); + } + if (!wb.isLoadedByAOTSafeCustomLoader(class01)) { + throw new RuntimeException("first class should have been loaded by AOT-safe loader"); + } + if (!wb.isLoadedByAOTSafeCustomLoader(class02)) { + throw new RuntimeException("second class should have been loaded by AOT-safe loader"); + } + } + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/SingleURLClassLoader.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/SingleURLClassLoader.java new file mode 100644 index 00000000000..d8cdf25eb9d --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/customLoader/test-classes/SingleURLClassLoader.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2026, 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. + * + */ + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import jdk.test.whitebox.WhiteBox; + +public class SingleURLClassLoader { + static URLClassLoader loader; + + public static void main(String args[]) throws Exception { + if (args.length < 2) { + throw new RuntimeException("insufficient arguments"); + } + URL url1 = new File(args[0]).toURI().toURL(); + URL url2 = new File(args[1]).toURI().toURL(); + URL[] urls = new URL[] {url1, url2}; + + loader = new URLClassLoader(urls); + + // CustomLoadee is present in classpath of the URLClassLoader and its parent (which is system loader); + // so the CustomLoadee should get loaded by the parent loader if URLClassLoader follows parent-first delegation model + Class class01 = loader.loadClass("CustomLoadee"); + // CustomLoadee3 is only present in the classpath of the URLClassLoader + Class class02 = loader.loadClass("CustomLoadee3"); + + System.out.println("class01 = " + class01); + System.out.println("class02 = " + class02); + + if (class01.getClassLoader() != ClassLoader.getSystemClassLoader()) { + throw new RuntimeException("CustomLoadee loaded by wrong loader"); + } + if (class02.getClassLoader() != loader) { + throw new RuntimeException("CustomLoadee3 loaded by wrong loader"); + } + + WhiteBox wb = WhiteBox.getWhiteBox(); + + if (!wb.isAOTSafeCustomLoader(loader)) { + throw new RuntimeException("loader should be marked as aot-safe"); + } + + if (wb.isSharedClass(SingleURLClassLoader.class)) { + if (!wb.isSharedClass(class01)) { + throw new RuntimeException("CustomLoadee class is not shared"); + } + if (!wb.isSharedClass(class02)) { + throw new RuntimeException("CustomLoadee3 class is not shared"); + } + if (!wb.isLoadedByBuiltinLoader(class01)) { + throw new RuntimeException("CustomLoadee should be loaded by builtin loader"); + } + if (!wb.isLoadedByAOTSafeCustomLoader(class02)) { + throw new RuntimeException("CustomLoadee3 should be loaded by aot-safe loader"); + } + } + } +} diff --git a/test/lib/jdk/test/whitebox/WhiteBox.java b/test/lib/jdk/test/whitebox/WhiteBox.java index c915cee41b0..424fd2bfcf7 100644 --- a/test/lib/jdk/test/whitebox/WhiteBox.java +++ b/test/lib/jdk/test/whitebox/WhiteBox.java @@ -799,6 +799,12 @@ public Object getMethodOption(Executable method, String name) { public native boolean cdsMemoryMappingFailed(); public native boolean isSharingEnabled(); public native boolean isSharedClass(Class c); + public native boolean isLoadedByBuiltinLoader(Class c); + public native boolean isLoadedByAOTSafeCustomLoader(Class c); + public native boolean isLoadedByAOTUnsafeCustomLoader(Class c); + public native boolean isAOTSafeCustomLoader(ClassLoader loader); + public native boolean areSharedStringsMapped(); + public native boolean isSharedInternedString(String s); public native boolean isCDSIncluded(); public native boolean isJFRIncluded(); public native boolean isDTraceIncluded();