diff --git a/src/hotspot/share/cds/aotMappedHeapLoader.cpp b/src/hotspot/share/cds/aotMappedHeapLoader.cpp index 146228436f4..210867be70c 100644 --- a/src/hotspot/share/cds/aotMappedHeapLoader.cpp +++ b/src/hotspot/share/cds/aotMappedHeapLoader.cpp @@ -360,10 +360,8 @@ bool AOTMappedHeapLoader::load_heap_region(FileMapInfo* mapinfo) { } objArrayOop AOTMappedHeapLoader::root_segment(int segment_idx) { - if (CDSConfig::is_dumping_heap()) { - assert(Thread::current() == (Thread*)VMThread::vm_thread(), "should be in vm thread"); - } else { - assert(CDSConfig::is_using_archive(), "must be"); + if (!CDSConfig::is_using_archive()) { + assert(CDSConfig::is_dumping_heap() && Thread::current() == (Thread*)VMThread::vm_thread(), "sanity"); } objArrayOop segment = (objArrayOop)_root_segments->at(segment_idx).resolve(); @@ -465,6 +463,10 @@ void AOTMappedHeapLoader::finish_initialization(FileMapInfo* info) { assert(segment_oop->is_objArray(), "Must be"); add_root_segment((objArrayOop)segment_oop); } + + if (CDSConfig::is_dumping_final_static_archive()) { + StringTable::move_shared_strings_into_runtime_table(); + } } } diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index 62d76957c0a..894a35183ca 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -1104,7 +1104,12 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { - assert(CDSConfig::allow_only_single_java_thread(), "Required"); + if (!CDSConfig::is_dumping_preimage_static_archive()) { + // A single thread is required for Reference handling and deterministic CDS archive. + // Its's not required for dumping preimage, where References won't be archived and + // determinism is not needed. + assert(CDSConfig::allow_only_single_java_thread(), "Required"); + } if (!HeapShared::is_archived_boot_layer_available(THREAD)) { report_loading_error("archivedBootLayer not available, disabling full module graph"); CDSConfig::stop_dumping_full_module_graph(); diff --git a/src/hotspot/share/cds/aotReferenceObjSupport.cpp b/src/hotspot/share/cds/aotReferenceObjSupport.cpp index aa7cc875533..0c27c8ce5f0 100644 --- a/src/hotspot/share/cds/aotReferenceObjSupport.cpp +++ b/src/hotspot/share/cds/aotReferenceObjSupport.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 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 @@ -177,12 +177,17 @@ void AOTReferenceObjSupport::init_keep_alive_objs_table() { // Returns true IFF obj is an instance of java.lang.ref.Reference. If so, perform extra eligibility checks. bool AOTReferenceObjSupport::check_if_ref_obj(oop obj) { - // We have a single Java thread. This means java.lang.ref.Reference$ReferenceHandler thread - // is not running. Otherwise the checks for next/discovered may not work. - precond(CDSConfig::allow_only_single_java_thread()); assert_at_safepoint(); // _keep_alive_objs_table uses raw oops if (obj->klass()->is_subclass_of(vmClasses::Reference_klass())) { + // The following check works only if the java.lang.ref.Reference$ReferenceHandler thread + // is not running. + // + // This code is called on every object found by AOTArtifactFinder. When dumping the + // preimage archive, AOTArtifactFinder should not find any Reference objects. + precond(!CDSConfig::is_dumping_preimage_static_archive()); + precond(CDSConfig::allow_only_single_java_thread()); + precond(AOTReferenceObjSupport::is_enabled()); precond(JavaClasses::is_supported_for_archiving(obj)); precond(_keep_alive_objs_table != nullptr); diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 5f6b568dd6e..f4ef3c66f7a 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -556,7 +556,9 @@ void CDSConfig::check_aotmode_record() { // At VM exit, the module graph may be contaminated with program states. // We will rebuild the module graph when dumping the CDS final image. - disable_heap_dumping(); + _is_using_optimized_module_handling = false; + _is_using_full_module_graph = false; + _is_dumping_full_module_graph = false; } void CDSConfig::check_aotmode_create() { @@ -582,6 +584,7 @@ void CDSConfig::check_aotmode_create() { substitute_aot_filename(FLAG_MEMBER_ENUM(AOTCache)); _is_dumping_final_static_archive = true; + _is_using_full_module_graph = false; UseSharedSpaces = true; RequireSharedSpaces = true; @@ -954,7 +957,9 @@ bool CDSConfig::are_vm_options_incompatible_with_dumping_heap() { } bool CDSConfig::is_dumping_heap() { - if (!(is_dumping_classic_static_archive() || is_dumping_final_static_archive()) + // Note: when dumping preimage static archive, only a very limited set of oops + // are dumped. + if (!is_dumping_static_archive() || are_vm_options_incompatible_with_dumping_heap() || _disable_heap_dumping) { return false; @@ -966,6 +971,26 @@ bool CDSConfig::is_loading_heap() { return HeapShared::is_archived_heap_in_use(); } +bool CDSConfig::is_dumping_klass_subgraphs() { + if (is_dumping_classic_static_archive() || is_dumping_final_static_archive()) { + // KlassSubGraphs (see heapShared.cpp) is a legacy mechanism for archiving oops. It + // has been superceded by AOT class linking. This feature is used only when + // AOT class linking is disabled. + // + // KlassSubGraphs are disabled in the preimage static archive, which contains a very + // limited set of oops. + return is_dumping_heap() && !is_dumping_aot_linked_classes(); + } else { + return false; + } +} + +bool CDSConfig::is_using_klass_subgraphs() { + return (is_loading_heap() && + !CDSConfig::is_using_aot_linked_classes() && + !CDSConfig::is_dumping_final_static_archive()); +} + bool CDSConfig::is_using_full_module_graph() { if (ClassLoaderDataShared::is_full_module_graph_loaded()) { return true; diff --git a/src/hotspot/share/cds/cdsConfig.hpp b/src/hotspot/share/cds/cdsConfig.hpp index 202904e8231..739dbb4937b 100644 --- a/src/hotspot/share/cds/cdsConfig.hpp +++ b/src/hotspot/share/cds/cdsConfig.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -188,6 +188,9 @@ public: static bool is_dumping_heap() NOT_CDS_JAVA_HEAP_RETURN_(false); static bool is_loading_heap() NOT_CDS_JAVA_HEAP_RETURN_(false); + static bool is_dumping_klass_subgraphs() NOT_CDS_JAVA_HEAP_RETURN_(false); + static bool is_using_klass_subgraphs() NOT_CDS_JAVA_HEAP_RETURN_(false); + static bool is_dumping_invokedynamic() NOT_CDS_JAVA_HEAP_RETURN_(false); static bool is_dumping_method_handles() NOT_CDS_JAVA_HEAP_RETURN_(false); diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/heapShared.cpp index 42129011612..143f9147853 100644 --- a/src/hotspot/share/cds/heapShared.cpp +++ b/src/hotspot/share/cds/heapShared.cpp @@ -210,7 +210,7 @@ static bool is_subgraph_root_class_of(ArchivableStaticFieldInfo fields[], Instan bool HeapShared::is_subgraph_root_class(InstanceKlass* ik) { assert(CDSConfig::is_dumping_heap(), "dump-time only"); - if (!CDSConfig::is_dumping_aot_linked_classes()) { + if (CDSConfig::is_dumping_klass_subgraphs()) { // Legacy CDS archive support (to be deprecated) return is_subgraph_root_class_of(archive_subgraph_entry_fields, ik) || is_subgraph_root_class_of(fmg_archive_subgraph_entry_fields, ik); @@ -455,7 +455,6 @@ int HeapShared::append_root(oop obj) { oop HeapShared::get_root(int index, bool clear) { assert(index >= 0, "sanity"); - assert(!CDSConfig::is_dumping_heap() && CDSConfig::is_using_archive(), "runtime only"); assert(is_archived_heap_in_use(), "getting roots into heap that is not used"); oop result; @@ -600,8 +599,7 @@ public: void set_oop(MetaspaceObj* ptr, oop o) { MutexLocker ml(ScratchObjects_lock, Mutex::_no_safepoint_check_flag); OopHandle handle(Universe::vm_global(), o); - bool is_new = put(ptr, handle); - assert(is_new, "cannot set twice"); + put_when_absent(ptr, handle); } void remove_oop(MetaspaceObj* ptr) { MutexLocker ml(ScratchObjects_lock, Mutex::_no_safepoint_check_flag); @@ -614,6 +612,11 @@ public: }; void HeapShared::add_scratch_resolved_references(ConstantPool* src, objArrayOop dest) { + if (CDSConfig::is_dumping_preimage_static_archive() && scratch_resolved_references(src) != nullptr) { + // We are in AOT training run. The class has been redefined and we are giving it a new resolved_reference. + // Ignore it, as this class will be excluded from the AOT config. + return; + } if (SystemDictionaryShared::is_builtin_loader(src->pool_holder()->class_loader_data())) { _scratch_objects_table->set_oop(src, dest); } @@ -934,7 +937,7 @@ void HeapShared::scan_java_class(Klass* orig_k) { void HeapShared::archive_subgraphs() { assert(CDSConfig::is_dumping_heap(), "must be"); - if (!CDSConfig::is_dumping_aot_linked_classes()) { + if (CDSConfig::is_dumping_klass_subgraphs()) { archive_object_subgraphs(archive_subgraph_entry_fields, false /* is_full_module_graph */); if (CDSConfig::is_dumping_full_module_graph()) { @@ -1292,10 +1295,7 @@ static void verify_the_heap(Klass* k, const char* which) { // this case, we will not load the ArchivedKlassSubGraphInfoRecord and will clear its roots. void HeapShared::resolve_classes(JavaThread* current) { assert(CDSConfig::is_using_archive(), "runtime only!"); - if (!is_archived_heap_in_use()) { - return; // nothing to do - } - if (!CDSConfig::is_using_aot_linked_classes()) { + if (CDSConfig::is_using_klass_subgraphs()) { resolve_classes_for_subgraphs(current, archive_subgraph_entry_fields); resolve_classes_for_subgraphs(current, fmg_archive_subgraph_entry_fields); } @@ -1385,7 +1385,7 @@ void HeapShared::init_classes_for_special_subgraph(Handle class_loader, TRAPS) { void HeapShared::initialize_from_archived_subgraph(JavaThread* current, Klass* k) { JavaThread* THREAD = current; - if (!is_archived_heap_in_use()) { + if (!CDSConfig::is_using_klass_subgraphs()) { return; // nothing to do } @@ -1861,7 +1861,7 @@ void HeapShared::archive_reachable_objects_from_static_field(InstanceKlass *k, const char* klass_name, int field_offset, const char* field_name) { - assert(CDSConfig::is_dumping_heap(), "dump time only"); + precond(CDSConfig::is_dumping_klass_subgraphs()); assert(k->defined_by_boot_loader(), "must be boot class"); oop m = k->java_mirror(); @@ -1912,7 +1912,7 @@ class VerifySharedOopClosure: public BasicOopIterateClosure { }; void HeapShared::verify_subgraph_from_static_field(InstanceKlass* k, int field_offset) { - assert(CDSConfig::is_dumping_heap(), "dump time only"); + precond(CDSConfig::is_dumping_klass_subgraphs()); assert(k->defined_by_boot_loader(), "must be boot class"); oop m = k->java_mirror(); @@ -2138,7 +2138,7 @@ void HeapShared::init_subgraph_entry_fields(ArchivableStaticFieldInfo fields[], void HeapShared::init_subgraph_entry_fields(TRAPS) { assert(CDSConfig::is_dumping_heap(), "must be"); _dump_time_subgraph_info_table = new (mtClass)DumpTimeKlassSubGraphInfoTable(); - if (!CDSConfig::is_dumping_aot_linked_classes()) { + if (CDSConfig::is_dumping_klass_subgraphs()) { init_subgraph_entry_fields(archive_subgraph_entry_fields, CHECK); if (CDSConfig::is_dumping_full_module_graph()) { init_subgraph_entry_fields(fmg_archive_subgraph_entry_fields, CHECK); diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index dd70d7b49ab..b650bf8cfb8 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -1263,6 +1263,10 @@ bool java_lang_Class::restore_archived_mirror(Klass *k, "Restored %s archived mirror " PTR_FORMAT, k->external_name(), p2i(mirror())); } + if (CDSConfig::is_dumping_heap()) { + create_scratch_mirror(k, CHECK_(false)); + } + return true; } #endif // INCLUDE_CDS_JAVA_HEAP diff --git a/src/hotspot/share/classfile/stringTable.cpp b/src/hotspot/share/classfile/stringTable.cpp index bbc12c8dcab..2b8b7780a41 100644 --- a/src/hotspot/share/classfile/stringTable.cpp +++ b/src/hotspot/share/classfile/stringTable.cpp @@ -987,4 +987,26 @@ void StringTable::serialize_shared_table_header(SerializeClosure* soc) { _shared_table.reset(); } } + +void StringTable::move_shared_strings_into_runtime_table() { + precond(CDSConfig::is_dumping_final_static_archive()); + JavaThread* THREAD = JavaThread::current(); + HandleMark hm(THREAD); + + int n = 0; + _shared_table.iterate_all([&](oop string) { + int length = java_lang_String::length(string); + Handle h_string (THREAD, string); + StringWrapper name(h_string, length); + unsigned int hash = hash_wrapped_string(name); + + assert(!_alt_hash, "too early"); + oop interned = do_intern(name, hash, THREAD); + assert(string == interned, "must be"); + n++; + }); + + _shared_table.reset(); + log_info(aot)("Moved %d interned strings to runtime table", n); +} #endif //INCLUDE_CDS_JAVA_HEAP diff --git a/src/hotspot/share/classfile/stringTable.hpp b/src/hotspot/share/classfile/stringTable.hpp index 94a0db5b5a5..0024a45a2f2 100644 --- a/src/hotspot/share/classfile/stringTable.hpp +++ b/src/hotspot/share/classfile/stringTable.hpp @@ -119,6 +119,7 @@ public: static void init_shared_table() NOT_CDS_JAVA_HEAP_RETURN; static void write_shared_table() NOT_CDS_JAVA_HEAP_RETURN; static void serialize_shared_table_header(SerializeClosure* soc) NOT_CDS_JAVA_HEAP_RETURN; + static void move_shared_strings_into_runtime_table(); // Jcmd static void dump(outputStream* st, bool verbose=false); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTMapTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTMapTest.java index 6cbfcbbd3c3..209eb064945 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTMapTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/AOTMapTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 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 @@ -136,6 +136,7 @@ public class AOTMapTest { } class AOTMapTestApp { + static URLClassLoader loader; // keep Hello class alive public static void main(String[] args) throws Exception { System.out.println("Hello AOTMapTestApp"); testCustomLoader(); @@ -144,7 +145,7 @@ class AOTMapTestApp { static void testCustomLoader() throws Exception { File custJar = new File("cust.jar"); URL[] urls = new URL[] {custJar.toURI().toURL()}; - URLClassLoader loader = new URLClassLoader(urls, AOTMapTestApp.class.getClassLoader()); + loader = new URLClassLoader(urls, AOTMapTestApp.class.getClassLoader()); Class c = loader.loadClass("Hello"); System.out.println(c); }