From 02ff38f2d7f6abc0e4661e8226bc6780b7a11c3a Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Wed, 19 Nov 2025 05:04:34 +0000 Subject: [PATCH] 8363986: Heap region in CDS archive is not at deterministic address Reviewed-by: kvn, asmehra --- src/hotspot/share/cds/aotArtifactFinder.hpp | 2 +- src/hotspot/share/cds/aotMapLogger.cpp | 2 +- src/hotspot/share/cds/aotMappedHeapWriter.cpp | 105 ++++++++++++++++-- src/hotspot/share/cds/aotMappedHeapWriter.hpp | 44 ++++---- src/hotspot/share/cds/filemap.cpp | 10 +- src/hotspot/share/cds/heapShared.hpp | 2 +- test/hotspot/jtreg/ProblemList.txt | 1 - 7 files changed, 125 insertions(+), 41 deletions(-) diff --git a/src/hotspot/share/cds/aotArtifactFinder.hpp b/src/hotspot/share/cds/aotArtifactFinder.hpp index 405222a8753..05bcde6b0ac 100644 --- a/src/hotspot/share/cds/aotArtifactFinder.hpp +++ b/src/hotspot/share/cds/aotArtifactFinder.hpp @@ -39,7 +39,7 @@ class TypeArrayKlass; // It also decides what Klasses must be cached in aot-initialized state. // // ArchiveBuilder uses [1] as roots to scan for all MetaspaceObjs that need to be cached. -// ArchiveHeapWriter uses [2] to create an image of the archived heap. +// HeapShared uses [2] to create an image of the archived heap. // // [1] is stored in _all_cached_classes in aotArtifactFinder.cpp. // [2] is stored in HeapShared::archived_object_cache(). diff --git a/src/hotspot/share/cds/aotMapLogger.cpp b/src/hotspot/share/cds/aotMapLogger.cpp index d0a63c56093..a252eae4b84 100644 --- a/src/hotspot/share/cds/aotMapLogger.cpp +++ b/src/hotspot/share/cds/aotMapLogger.cpp @@ -796,7 +796,7 @@ void AOTMapLogger::dumptime_log_mapped_heap_region(ArchiveMappedHeapInfo* heap_i address buffer_start = address(r.start()); // start of the current oop inside the buffer address buffer_end = address(r.end()); - address requested_base = UseCompressedOops ? (address)CompressedOops::base() : (address)AOTMappedHeapWriter::NOCOOPS_REQUESTED_BASE; + address requested_base = UseCompressedOops ? AOTMappedHeapWriter::narrow_oop_base() : (address)AOTMappedHeapWriter::NOCOOPS_REQUESTED_BASE; address requested_start = UseCompressedOops ? AOTMappedHeapWriter::buffered_addr_to_requested_addr(buffer_start) : requested_base; log_region_range("heap", buffer_start, buffer_end, requested_start); diff --git a/src/hotspot/share/cds/aotMappedHeapWriter.cpp b/src/hotspot/share/cds/aotMappedHeapWriter.cpp index ff9319d266b..98f400c989c 100644 --- a/src/hotspot/share/cds/aotMappedHeapWriter.cpp +++ b/src/hotspot/share/cds/aotMappedHeapWriter.cpp @@ -55,7 +55,7 @@ GrowableArrayCHeap* AOTMappedHeapWriter::_buffer = nullptr; -// The following are offsets from buffer_bottom() +bool AOTMappedHeapWriter::_is_writing_deterministic_heap = false; size_t AOTMappedHeapWriter::_buffer_used; // Heap root segments @@ -74,7 +74,7 @@ AOTMappedHeapWriter::_buffer_offset_to_source_obj_table = nullptr; DumpedInternedStrings *AOTMappedHeapWriter::_dumped_interned_strings = nullptr; typedef HashTable< - size_t, // offset of a filler from ArchiveHeapWriter::buffer_bottom() + size_t, // offset of a filler from AOTMappedHeapWriter::buffer_bottom() size_t, // size of this filler (in bytes) 127, // prime number AnyObj::C_HEAP, @@ -96,6 +96,45 @@ void AOTMappedHeapWriter::init() { _source_objs = new GrowableArrayCHeap(10000); guarantee(MIN_GC_REGION_ALIGNMENT <= G1HeapRegion::min_region_size_in_words() * HeapWordSize, "must be"); + + if (CDSConfig::old_cds_flags_used()) { + // With the old CDS workflow, we can guatantee determninistic output: given + // the same classlist file, we can generate the same static CDS archive. + // To ensure determinism, we always use the same compressed oop encoding + // (zero-based, no shift). See set_requested_address_range(). + _is_writing_deterministic_heap = true; + } else { + // Determninistic output is not supported by the new AOT workflow, so + // we don't force the (zero-based, no shift) encoding. This way, it is more + // likely that we can avoid oop relocation in the production run. + _is_writing_deterministic_heap = false; + } + } +} + +// For AOTMappedHeapWriter::narrow_oop_{mode, base, shift}(), see comments +// in AOTMappedHeapWriter::set_requested_address_range(), +CompressedOops::Mode AOTMappedHeapWriter::narrow_oop_mode() { + if (is_writing_deterministic_heap()) { + return CompressedOops::UnscaledNarrowOop; + } else { + return CompressedOops::mode(); + } +} + +address AOTMappedHeapWriter::narrow_oop_base() { + if (is_writing_deterministic_heap()) { + return (address)0; + } else { + return CompressedOops::base(); + } +} + +int AOTMappedHeapWriter::narrow_oop_shift() { + if (is_writing_deterministic_heap()) { + return 0; + } else { + return CompressedOops::shift(); } } @@ -116,7 +155,7 @@ void AOTMappedHeapWriter::write(GrowableArrayCHeap* roots, assert(CDSConfig::is_dumping_heap(), "sanity"); allocate_buffer(); copy_source_objs_to_buffer(roots); - set_requested_address(heap_info); + set_requested_address_range(heap_info); relocate_embedded_oops(roots, heap_info); } @@ -536,14 +575,55 @@ size_t AOTMappedHeapWriter::copy_one_source_obj_to_buffer(oop src_obj) { return buffered_obj_offset; } -void AOTMappedHeapWriter::set_requested_address(ArchiveMappedHeapInfo* info) { +// Set the range [_requested_bottom, _requested_top), the requested address range of all +// the archived heap objects in the production run. +// +// (1) UseCompressedOops == true && !is_writing_deterministic_heap() +// +// The archived objects are stored using the COOPS encoding of the assembly phase. +// We pick a range within the heap used by the assembly phase. +// +// In the production run, if different COOPS encodings are used: +// - The heap contents needs to be relocated. +// +// (2) UseCompressedOops == true && is_writing_deterministic_heap() +// +// We always use zero-based, zero-shift encoding. _requested_top is aligned to 0x10000000. +// +// (3) UseCompressedOops == false: +// +// In the production run, the heap range is usually picked (randomly) by the OS, so we +// will almost always need to perform relocation, regardless of how we pick the requested +// address range. +// +// So we just hard code it to NOCOOPS_REQUESTED_BASE. +// +void AOTMappedHeapWriter::set_requested_address_range(ArchiveMappedHeapInfo* info) { assert(!info->is_used(), "only set once"); size_t heap_region_byte_size = _buffer_used; assert(heap_region_byte_size > 0, "must archived at least one object!"); if (UseCompressedOops) { - if (UseG1GC) { + if (is_writing_deterministic_heap()) { + // Pick a heap range so that requested addresses can be encoded with zero-base/no shift. + // We align the requested bottom to at least 1 MB: if the production run uses G1 with a small + // heap (e.g., -Xmx256m), it's likely that we can map the archived objects at the + // requested location to avoid relocation. + // + // For other collectors or larger heaps, relocation is unavoidable, but is usually + // quite cheap. If you really want to avoid relocation, use the AOT workflow instead. + address heap_end = (address)0x100000000; + size_t alignment = MAX2(MIN_GC_REGION_ALIGNMENT, 1024 * 1024); + if (align_up(heap_region_byte_size, alignment) >= (size_t)heap_end) { + log_error(aot, heap)("cached heap space is too large: %zu bytes", heap_region_byte_size); + AOTMetaspace::unrecoverable_writing_error(); + } + _requested_bottom = align_down(heap_end - heap_region_byte_size, alignment); + } else if (UseG1GC) { + // For G1, pick the range at the top of the current heap. If the exact same heap sizes + // are used in the production run, it's likely that we can map the archived objects + // at the requested location to avoid relocation. address heap_end = (address)G1CollectedHeap::heap()->reserved().end(); log_info(aot, heap)("Heap end = %p", heap_end); _requested_bottom = align_down(heap_end - heap_region_byte_size, G1HeapRegion::GrainBytes); @@ -612,7 +692,14 @@ oop AOTMappedHeapWriter::load_oop_from_buffer(narrowOop* buffered_addr) { template void AOTMappedHeapWriter::relocate_field_in_buffer(T* field_addr_in_buffer, oop source_referent, CHeapBitMap* oopmap) { oop request_referent = source_obj_to_requested_obj(source_referent); - store_requested_oop_in_buffer(field_addr_in_buffer, request_referent); + if (UseCompressedOops && is_writing_deterministic_heap()) { + // We use zero-based, 0-shift encoding, so the narrowOop is just the lower + // 32 bits of request_referent + intptr_t addr = cast_from_oop(request_referent); + *((narrowOop*)field_addr_in_buffer) = checked_cast(addr); + } else { + store_requested_oop_in_buffer(field_addr_in_buffer, request_referent); + } if (request_referent != nullptr) { mark_oop_pointer(field_addr_in_buffer, oopmap); } @@ -918,9 +1005,9 @@ AOTMapLogger::OopDataIterator* AOTMappedHeapWriter::oop_iterator(ArchiveMappedHe address buffer_start = address(r.start()); address buffer_end = address(r.end()); - address requested_base = UseCompressedOops ? (address)CompressedOops::base() : (address)AOTMappedHeapWriter::NOCOOPS_REQUESTED_BASE; - address requested_start = UseCompressedOops ? buffered_addr_to_requested_addr(buffer_start) : requested_base; - int requested_shift = CompressedOops::shift(); + address requested_base = UseCompressedOops ? AOTMappedHeapWriter::narrow_oop_base() : (address)AOTMappedHeapWriter::NOCOOPS_REQUESTED_BASE; + address requested_start = UseCompressedOops ? AOTMappedHeapWriter::buffered_addr_to_requested_addr(buffer_start) : requested_base; + int requested_shift = AOTMappedHeapWriter::narrow_oop_shift(); intptr_t buffer_to_requested_delta = requested_start - buffer_start; uint64_t buffer_start_narrow_oop = 0xdeadbeed; if (UseCompressedOops) { diff --git a/src/hotspot/share/cds/aotMappedHeapWriter.hpp b/src/hotspot/share/cds/aotMappedHeapWriter.hpp index 9a85b83d3d1..eafd38ac8bb 100644 --- a/src/hotspot/share/cds/aotMappedHeapWriter.hpp +++ b/src/hotspot/share/cds/aotMappedHeapWriter.hpp @@ -29,6 +29,7 @@ #include "cds/heapShared.hpp" #include "memory/allocation.hpp" #include "memory/allStatic.hpp" +#include "oops/compressedOops.hpp" #include "oops/oopHandle.hpp" #include "utilities/bitMap.hpp" #include "utilities/exceptions.hpp" @@ -71,7 +72,7 @@ class AOTMappedHeapWriter : AllStatic { // These are entered into HeapShared::archived_object_cache(). // // - "buffered objects" are copies of the "source objects", and are stored in into - // ArchiveHeapWriter::_buffer, which is a GrowableArray that sits outside of + // AOTMappedHeapWriter::_buffer, which is a GrowableArray that sits outside of // the valid heap range. Therefore we avoid using the addresses of these copies // as oops. They are usually called "buffered_addr" in the code (of the type "address"). // @@ -81,26 +82,11 @@ class AOTMappedHeapWriter : AllStatic { // - Each archived object has a "requested address" -- at run time, if the object // can be mapped at this address, we can avoid relocation. // - // The requested address is implemented differently depending on UseCompressedOops: + // The requested address of an archived object is essentially its buffered_addr + delta, + // where delta is (_requested_bottom - buffer_bottom()); // - // UseCompressedOops == true: - // The archived objects are stored assuming that the runtime COOPS compression - // scheme is exactly the same as in dump time (or else a more expensive runtime relocation - // would be needed.) - // - // At dump time, we assume that the runtime heap range is exactly the same as - // in dump time. The requested addresses of the archived objects are chosen such that - // they would occupy the top end of a G1 heap (TBD when dumping is supported by other - // collectors. See JDK-8298614). - // - // UseCompressedOops == false: - // At runtime, the heap range is usually picked (randomly) by the OS, so we will almost always - // need to perform relocation. Hence, the goal of the "requested address" is to ensure that - // the contents of the archived objects are deterministic. I.e., the oop fields of archived - // objects will always point to deterministic addresses. - // - // For G1, the archived heap is written such that the lowest archived object is placed - // at NOCOOPS_REQUESTED_BASE. (TBD after JDK-8298614). + // The requested addresses of all archived objects are within [_requested_bottom, _requested_top). + // See AOTMappedHeapWriter::set_requested_address_range() for more info. // ---------------------------------------------------------------------- public: @@ -111,6 +97,15 @@ public: // Shenandoah heap region size can never be smaller than 256K. static constexpr int MIN_GC_REGION_ALIGNMENT = 256 * K; + // The heap contents are required to be deterministic when dumping "old" CDS archives, in order + // to support reproducible lib/server/classes*.jsa when building the JDK. + static bool is_writing_deterministic_heap() { return _is_writing_deterministic_heap; } + + // The oop encoding used by the archived heap objects. + static CompressedOops::Mode narrow_oop_mode(); + static address narrow_oop_base(); + static int narrow_oop_shift(); + static const int INITIAL_TABLE_SIZE = 15889; // prime number static const int MAX_TABLE_SIZE = 1000000; @@ -121,6 +116,7 @@ private: int _field_offset; }; + static bool _is_writing_deterministic_heap; static GrowableArrayCHeap* _buffer; // The number of bytes that have written into _buffer (may be smaller than _buffer->length()). @@ -130,15 +126,15 @@ private: static HeapRootSegments _heap_root_segments; // The address range of the requested location of the archived heap objects. - static address _requested_bottom; - static address _requested_top; + static address _requested_bottom; // The requested address of the lowest archived heap object + static address _requested_top; // The exclusive end of the highest archived heap object static GrowableArrayCHeap* _native_pointers; static GrowableArrayCHeap* _source_objs; static DumpedInternedStrings *_dumped_interned_strings; // We sort _source_objs_order to minimize the number of bits in ptrmap and oopmap. - // See comments near the body of ArchiveHeapWriter::compare_objs_by_oop_fields(). + // See comments near the body of AOTMappedHeapWriter::compare_objs_by_oop_fields(). // The objects will be written in the order of: //_source_objs->at(_source_objs_order->at(0)._index) // source_objs->at(_source_objs_order->at(1)._index) @@ -200,7 +196,7 @@ private: static int filler_array_length(size_t fill_bytes); static HeapWord* init_filler_array_at_buffer_top(int array_length, size_t fill_bytes); - static void set_requested_address(ArchiveMappedHeapInfo* info); + static void set_requested_address_range(ArchiveMappedHeapInfo* info); static void mark_native_pointers(oop orig_obj); static void relocate_embedded_oops(GrowableArrayCHeap* roots, ArchiveMappedHeapInfo* info); static void compute_ptrmap(ArchiveMappedHeapInfo *info); diff --git a/src/hotspot/share/cds/filemap.cpp b/src/hotspot/share/cds/filemap.cpp index ae92ce31058..61df0a86b41 100644 --- a/src/hotspot/share/cds/filemap.cpp +++ b/src/hotspot/share/cds/filemap.cpp @@ -216,12 +216,14 @@ void FileMapHeader::populate(FileMapInfo *info, size_t core_region_alignment, _obj_alignment = ObjectAlignmentInBytes; _compact_strings = CompactStrings; _compact_headers = UseCompactObjectHeaders; +#if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { _object_streaming_mode = HeapShared::is_writing_streaming_mode(); - _narrow_oop_mode = CompressedOops::mode(); - _narrow_oop_base = CompressedOops::base(); - _narrow_oop_shift = CompressedOops::shift(); + _narrow_oop_mode = AOTMappedHeapWriter::narrow_oop_mode(); + _narrow_oop_base = AOTMappedHeapWriter::narrow_oop_base(); + _narrow_oop_shift = AOTMappedHeapWriter::narrow_oop_shift(); } +#endif _compressed_oops = UseCompressedOops; _compressed_class_ptrs = UseCompressedClassPointers; if (UseCompressedClassPointers) { @@ -911,7 +913,7 @@ void FileMapInfo::write_region(int region, char* base, size_t size, if (HeapShared::is_writing_mapping_mode()) { requested_base = (char*)AOTMappedHeapWriter::requested_address(); if (UseCompressedOops) { - mapping_offset = (size_t)((address)requested_base - CompressedOops::base()); + mapping_offset = (size_t)((address)requested_base - AOTMappedHeapWriter::narrow_oop_base()); assert((mapping_offset >> CompressedOops::shift()) << CompressedOops::shift() == mapping_offset, "must be"); } } else { diff --git a/src/hotspot/share/cds/heapShared.hpp b/src/hotspot/share/cds/heapShared.hpp index 2c782f7231b..118c60faa60 100644 --- a/src/hotspot/share/cds/heapShared.hpp +++ b/src/hotspot/share/cds/heapShared.hpp @@ -332,7 +332,7 @@ public: // Used by CDSHeapVerifier. OopHandle _orig_referrer; - // The location of this object inside ArchiveHeapWriter::_buffer + // The location of this object inside {AOTMappedHeapWriter, AOTStreamedHeapWriter}::_buffer size_t _buffer_offset; // One or more fields in this object are pointing to non-null oops. diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index a45fdc09323..934ef03a987 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -100,7 +100,6 @@ gc/shenandoah/TestSieveObjects.java#no-tlab-genshen 8361099 generic-all # :hotspot_runtime -runtime/cds/DeterministicDump.java 8363986 macosx-x64,macosx-aarch64 runtime/jni/terminatedThread/TestTerminatedThread.java 8317789 aix-ppc64 runtime/Monitor/SyncOnValueBasedClassTest.java 8340995 linux-s390x runtime/os/TestTracePageSizes.java#no-options 8267460 linux-aarch64