8301106: Allow archived Java strings to be moved by GC

Reviewed-by: dholmes
This commit is contained in:
Ioi Lam 2023-03-29 23:42:52 +00:00
parent 9643f654da
commit b524a74165
13 changed files with 239 additions and 147 deletions

View file

@ -40,6 +40,7 @@
#include "logging/log.hpp"
#include "logging/logStream.hpp"
#include "memory/allocation.inline.hpp"
#include "memory/oopFactory.hpp"
#include "memory/resourceArea.hpp"
#include "oops/access.inline.hpp"
#include "oops/compressedOops.hpp"
@ -72,26 +73,31 @@ const size_t REHASH_LEN = 100;
const double CLEAN_DEAD_HIGH_WATER_MARK = 0.5;
#if INCLUDE_CDS_JAVA_HEAP
inline oop read_string_from_compact_hashtable(address base_address, u4 offset) {
assert(ArchiveHeapLoader::are_archived_strings_available(), "sanity");
if (UseCompressedOops) {
assert(sizeof(narrowOop) == sizeof(offset), "must be");
narrowOop v = CompressedOops::narrow_oop_cast(offset);
return ArchiveHeapLoader::decode_from_archive(v);
bool StringTable::_is_two_dimensional_shared_strings_array = false;
OopHandle StringTable::_shared_strings_array;
int StringTable::_shared_strings_array_root_index;
inline oop StringTable::read_string_from_compact_hashtable(address base_address, u4 index) {
assert(ArchiveHeapLoader::is_in_use(), "sanity");
objArrayOop array = (objArrayOop)(_shared_strings_array.resolve());
oop s;
if (!_is_two_dimensional_shared_strings_array) {
s = array->obj_at((int)index);
} else {
assert(!ArchiveHeapLoader::is_loaded(), "Pointer relocation for uncompressed oops is unimplemented");
intptr_t dumptime_oop = (uintptr_t)offset;
assert(dumptime_oop != 0, "null strings cannot be interned");
intptr_t runtime_oop = dumptime_oop +
(intptr_t)FileMapInfo::current_info()->header()->heap_begin() +
(intptr_t)ArchiveHeapLoader::mapped_heap_delta();
return (oop)cast_to_oop(runtime_oop);
int primary_index = index >> _secondary_array_index_bits;
int secondary_index = index & _secondary_array_index_mask;
objArrayOop secondary = (objArrayOop)array->obj_at(primary_index);
s = secondary->obj_at(secondary_index);
}
assert(java_lang_String::is_instance(s), "must be");
return s;
}
typedef CompactHashtable<
const jchar*, oop,
read_string_from_compact_hashtable,
StringTable::read_string_from_compact_hashtable,
java_lang_String::equals> SharedStringTable;
static SharedStringTable _shared_table;
@ -224,6 +230,12 @@ void StringTable::create_table() {
_local_table = new StringTableHash(start_size_log_2, END_SIZE, REHASH_LEN, true);
_oop_storage = OopStorageSet::create_weak("StringTable Weak", mtSymbol);
_oop_storage->register_num_dead_callback(&gc_notification);
#if INCLUDE_CDS_JAVA_HEAP
if (ArchiveHeapLoader::is_in_use()) {
_shared_strings_array = OopHandle(Universe::vm_global(), HeapShared::get_root(_shared_strings_array_root_index));
}
#endif
}
size_t StringTable::item_added() {
@ -755,49 +767,131 @@ oop StringTable::lookup_shared(const jchar* name, int len) {
return _shared_table.lookup(name, java_lang_String::hash_code(name, len), len);
}
class EncodeSharedStringsAsOffsets : StackObj {
CompactHashtableWriter* _writer;
private:
u4 compute_delta(oop s) {
HeapWord* start = G1CollectedHeap::heap()->reserved().start();
intx offset = ((address)(void*)s) - ((address)(void*)start);
assert(offset >= 0, "must be");
if (offset > 0xffffffff) {
fatal("too large");
}
return (u4)offset;
// This is called BEFORE we enter the CDS safepoint. We can allocate heap objects.
// This should be called when we know no more strings will be added (which will be easy
// to guarantee because CDS runs with a single Java thread. See JDK-8253495.)
void StringTable::allocate_shared_strings_array(TRAPS) {
assert(DumpSharedSpaces, "must be");
if (_items_count > (size_t)max_jint) {
fatal("Too many strings to be archived: " SIZE_FORMAT, _items_count);
}
public:
EncodeSharedStringsAsOffsets(CompactHashtableWriter* writer) : _writer(writer) {}
bool do_entry(oop s, bool value_ignored) {
assert(s != nullptr, "sanity");
assert(!ArchiveHeapWriter::is_string_too_large_to_archive(s), "must be");
oop req_s = ArchiveHeapWriter::source_obj_to_requested_obj(s);
assert(req_s != nullptr, "must have been archived");
unsigned int hash = java_lang_String::hash_code(s);
if (UseCompressedOops) {
_writer->add(hash, CompressedOops::narrow_oop_value(req_s));
} else {
_writer->add(hash, compute_delta(req_s));
}
return true; // keep iterating
}
};
// Write the _shared_table (a CompactHashtable) into the CDS archive file.
void StringTable::write_shared_table(const DumpedInternedStrings* dumped_interned_strings) {
int total = (int)_items_count;
size_t single_array_size = objArrayOopDesc::object_size(total);
log_info(cds)("allocated string table for %d strings", total);
if (!ArchiveHeapWriter::is_too_large_to_archive(single_array_size)) {
// The entire table can fit in a single array
objArrayOop array = oopFactory::new_objArray(vmClasses::Object_klass(), total, CHECK);
_shared_strings_array = OopHandle(Universe::vm_global(), array);
log_info(cds)("string table array (single level) length = %d", total);
} else {
// Split the table in two levels of arrays.
int primary_array_length = (total + _secondary_array_max_length - 1) / _secondary_array_max_length;
size_t primary_array_size = objArrayOopDesc::object_size(primary_array_length);
size_t secondary_array_size = objArrayOopDesc::object_size(_secondary_array_max_length);
if (ArchiveHeapWriter::is_too_large_to_archive(secondary_array_size)) {
// This can only happen if you have an extremely large number of classes that
// refer to more than 16384 * 16384 = 26M interned strings! Not a practical concern
// but bail out for safety.
log_error(cds)("Too many strings to be archived: " SIZE_FORMAT, _items_count);
os::_exit(1);
}
objArrayOop primary = oopFactory::new_objArray(vmClasses::Object_klass(), primary_array_length, CHECK);
objArrayHandle primaryHandle(THREAD, primary);
_shared_strings_array = OopHandle(Universe::vm_global(), primary);
log_info(cds)("string table array (primary) length = %d", primary_array_length);
for (int i = 0; i < primary_array_length; i++) {
int len;
if (total > _secondary_array_max_length) {
len = _secondary_array_max_length;
} else {
len = total;
}
total -= len;
objArrayOop secondary = oopFactory::new_objArray(vmClasses::Object_klass(), len, CHECK);
primaryHandle()->obj_at_put(i, secondary);
log_info(cds)("string table array (secondary)[%d] length = %d", i, len);
assert(!ArchiveHeapWriter::is_too_large_to_archive(secondary), "sanity");
}
assert(total == 0, "must be");
_is_two_dimensional_shared_strings_array = true;
}
}
#ifndef PRODUCT
void StringTable::verify_secondary_array_index_bits() {
int max;
for (max = 1; ; max++) {
size_t next_size = objArrayOopDesc::object_size(1 << (max + 1));
if (ArchiveHeapWriter::is_too_large_to_archive(next_size)) {
break;
}
}
// Currently max is 17 for +UseCompressedOops, 16 for -UseCompressedOops.
// When we add support for Shenandoah (which has a smaller mininum region size than G1),
// max will become 15/14.
//
// We use _secondary_array_index_bits==14 as that will be the eventual value, and will
// make testing easier.
assert(_secondary_array_index_bits <= max,
"_secondary_array_index_bits (%d) must be smaller than max possible value (%d)",
_secondary_array_index_bits, max);
}
#endif // PRODUCT
// This is called AFTER we enter the CDS safepoint.
//
// For each shared string:
// [1] Store it into _shared_strings_array. Encode its position as a 32-bit index.
// [2] Store the index and hashcode into _shared_table.
oop StringTable::init_shared_table(const DumpedInternedStrings* dumped_interned_strings) {
assert(HeapShared::can_write(), "must be");
objArrayOop array = (objArrayOop)(_shared_strings_array.resolve());
verify_secondary_array_index_bits();
_shared_table.reset();
CompactHashtableWriter writer(_items_count, ArchiveBuilder::string_stats());
// Encode the strings in the CompactHashtable using offsets -- we know that the
// strings will not move during runtime because they are inside the G1 closed
// archive region.
EncodeSharedStringsAsOffsets offset_finder(&writer);
dumped_interned_strings->iterate(&offset_finder);
int index = 0;
auto copy_into_array = [&] (oop string, bool value_ignored) {
unsigned int hash = java_lang_String::hash_code(string);
writer.add(hash, index);
if (!_is_two_dimensional_shared_strings_array) {
assert(index < array->length(), "no strings should have been added");
array->obj_at_put(index, string);
} else {
int primary_index = index >> _secondary_array_index_bits;
int secondary_index = index & _secondary_array_index_mask;
assert(primary_index < array->length(), "no strings should have been added");
objArrayOop secondary = (objArrayOop)array->obj_at(primary_index);
assert(secondary != nullptr && secondary->is_objArray(), "must be");
assert(secondary_index < secondary->length(), "no strings should have been added");
secondary->obj_at_put(secondary_index, string);
}
index ++;
};
dumped_interned_strings->iterate_all(copy_into_array);
writer.dump(&_shared_table, "string");
return array;
}
void StringTable::set_shared_strings_array_index(int root_index) {
_shared_strings_array_root_index = root_index;
}
void StringTable::serialize_shared_table_header(SerializeClosure* soc) {
@ -806,47 +900,11 @@ void StringTable::serialize_shared_table_header(SerializeClosure* soc) {
if (soc->writing()) {
// Sanity. Make sure we don't use the shared table at dump time
_shared_table.reset();
} else if (!ArchiveHeapLoader::are_archived_strings_available()) {
} else if (!ArchiveHeapLoader::is_in_use()) {
_shared_table.reset();
}
soc->do_bool(&_is_two_dimensional_shared_strings_array);
soc->do_u4((u4*)(&_shared_strings_array_root_index));
}
class SharedStringTransfer {
JavaThread* _current;
public:
SharedStringTransfer(JavaThread* current) : _current(current) {}
void do_value(oop string) {
JavaThread* THREAD = _current;
ExceptionMark rm(THREAD);
HandleMark hm(THREAD);
StringTable::intern(string, THREAD);
if (HAS_PENDING_EXCEPTION) {
// The archived constant pools contains strings that must be in the interned string table.
// If we fail here, it means the VM runs out of memory during bootstrap, so there's no point
// of trying to recover from here.
vm_exit_during_initialization("Failed to transfer shared strings to interned string table");
}
}
};
// If the CDS archive heap is loaded (not mapped) into the old generation,
// it's possible for the shared strings to move due to full GC, making the
// _shared_table invalid. Therefore, we proactively copy all the shared
// strings into the _local_table, which can deal with oop relocation.
void StringTable::transfer_shared_strings_to_local_table() {
assert(ArchiveHeapLoader::is_loaded(), "must be");
EXCEPTION_MARK;
// Reset _shared_table so that during the transfer, StringTable::intern()
// will not look up from there. Instead, it will create a new entry in
// _local_table for each element in shared_table_copy.
SharedStringTable shared_table_copy = _shared_table;
_shared_table.reset();
SharedStringTransfer transfer(THREAD);
shared_table_copy.iterate(&transfer);
}
#endif //INCLUDE_CDS_JAVA_HEAP