8363986: Heap region in CDS archive is not at deterministic address

Reviewed-by: kvn, asmehra
This commit is contained in:
Ioi Lam 2025-11-19 05:04:34 +00:00
parent 902aa4dcd2
commit 02ff38f2d7
7 changed files with 125 additions and 41 deletions

View File

@ -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().

View File

@ -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);

View File

@ -55,7 +55,7 @@
GrowableArrayCHeap<u1, mtClassShared>* 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<oop, mtClassShared>(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<oop, mtClassShared>* 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 <typename T> 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<T>(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<intptr_t>(request_referent);
*((narrowOop*)field_addr_in_buffer) = checked_cast<narrowOop>(addr);
} else {
store_requested_oop_in_buffer<T>(field_addr_in_buffer, request_referent);
}
if (request_referent != nullptr) {
mark_oop_pointer<T>(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) {

View File

@ -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<u1, mtClassShared>* _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<NativePointerInfo, mtClassShared>* _native_pointers;
static GrowableArrayCHeap<oop, mtClassShared>* _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<oop, mtClassShared>* roots, ArchiveMappedHeapInfo* info);
static void compute_ptrmap(ArchiveMappedHeapInfo *info);

View File

@ -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 {

View File

@ -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.

View File

@ -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