8319650: Improve heap dump performance with class metadata caching

Reviewed-by: simonis, coleenp, yyang
This commit is contained in:
Aleksey Shipilev 2023-11-13 11:51:38 +00:00
parent b41b00a41c
commit 03db82818b

View File

@ -722,8 +722,10 @@ void DumpWriter::do_compress() {
} }
} }
// Support class with a collection of functions used when dumping the heap class DumperClassCacheTable;
class DumperClassCacheTableEntry;
// Support class with a collection of functions used when dumping the heap
class DumperSupport : AllStatic { class DumperSupport : AllStatic {
public: public:
@ -738,7 +740,7 @@ class DumperSupport : AllStatic {
static u4 sig2size(Symbol* sig); static u4 sig2size(Symbol* sig);
// returns the size of the instance of the given class // returns the size of the instance of the given class
static u4 instance_size(Klass* k); static u4 instance_size(InstanceKlass* ik, DumperClassCacheTableEntry* class_cache_entry = nullptr);
// dump a jfloat // dump a jfloat
static void dump_float(AbstractDumpWriter* writer, jfloat f); static void dump_float(AbstractDumpWriter* writer, jfloat f);
@ -751,13 +753,13 @@ class DumperSupport : AllStatic {
// dumps static fields of the given class // dumps static fields of the given class
static void dump_static_fields(AbstractDumpWriter* writer, Klass* k); static void dump_static_fields(AbstractDumpWriter* writer, Klass* k);
// dump the raw values of the instance fields of the given object // dump the raw values of the instance fields of the given object
static void dump_instance_fields(AbstractDumpWriter* writer, oop o); static void dump_instance_fields(AbstractDumpWriter* writer, oop o, DumperClassCacheTableEntry* class_cache_entry);
// get the count of the instance fields for a given class // get the count of the instance fields for a given class
static u2 get_instance_fields_count(InstanceKlass* ik); static u2 get_instance_fields_count(InstanceKlass* ik);
// dumps the definition of the instance fields for a given class // dumps the definition of the instance fields for a given class
static void dump_instance_field_descriptors(AbstractDumpWriter* writer, Klass* k); static void dump_instance_field_descriptors(AbstractDumpWriter* writer, Klass* k);
// creates HPROF_GC_INSTANCE_DUMP record for the given object // creates HPROF_GC_INSTANCE_DUMP record for the given object
static void dump_instance(AbstractDumpWriter* writer, oop o); static void dump_instance(AbstractDumpWriter* writer, oop o, DumperClassCacheTable* class_cache);
// creates HPROF_GC_CLASS_DUMP record for the given instance class // creates HPROF_GC_CLASS_DUMP record for the given instance class
static void dump_instance_class(AbstractDumpWriter* writer, Klass* k); static void dump_instance_class(AbstractDumpWriter* writer, Klass* k);
// creates HPROF_GC_CLASS_DUMP record for a given array class // creates HPROF_GC_CLASS_DUMP record for a given array class
@ -787,6 +789,106 @@ class DumperSupport : AllStatic {
} }
}; };
// Hash table of klasses to the klass metadata. This should greatly improve the
// hash dumping performance. This hash table is supposed to be used by a single
// thread only.
//
class DumperClassCacheTableEntry : public CHeapObj<mtServiceability> {
friend class DumperClassCacheTable;
private:
GrowableArray<char> _sigs_start;
GrowableArray<int> _offsets;
u4 _instance_size;
int _entries;
public:
DumperClassCacheTableEntry() : _instance_size(0), _entries(0) {};
int field_count() { return _entries; }
char sig_start(int field_idx) { return _sigs_start.at(field_idx); }
int offset(int field_idx) { return _offsets.at(field_idx); }
u4 instance_size() { return _instance_size; }
};
class DumperClassCacheTable {
private:
// ResourceHashtable SIZE is specified at compile time so we
// use 1031 which is the first prime after 1024.
static constexpr size_t TABLE_SIZE = 1031;
// Maintain the cache for N classes. This limits memory footprint
// impact, regardless of how many classes we have in the dump.
// This also improves look up performance by keeping the statically
// sized table from overloading.
static constexpr int CACHE_TOP = 256;
typedef ResourceHashtable<InstanceKlass*, DumperClassCacheTableEntry*,
TABLE_SIZE, AnyObj::C_HEAP, mtServiceability> PtrTable;
PtrTable* _ptrs;
// Single-slot cache to handle the major case of objects of the same
// class back-to-back, e.g. from T[].
InstanceKlass* _last_ik;
DumperClassCacheTableEntry* _last_entry;
void unlink_all(PtrTable* table) {
class CleanupEntry: StackObj {
public:
bool do_entry(InstanceKlass*& key, DumperClassCacheTableEntry*& entry) {
delete entry;
return true;
}
} cleanup;
table->unlink(&cleanup);
}
public:
DumperClassCacheTableEntry* lookup_or_create(InstanceKlass* ik) {
if (_last_ik == ik) {
return _last_entry;
}
DumperClassCacheTableEntry* entry;
DumperClassCacheTableEntry** from_cache = _ptrs->get(ik);
if (from_cache == nullptr) {
entry = new DumperClassCacheTableEntry();
for (HierarchicalFieldStream<JavaFieldStream> fld(ik); !fld.done(); fld.next()) {
if (!fld.access_flags().is_static()) {
Symbol* sig = fld.signature();
entry->_sigs_start.push(sig->char_at(0));
entry->_offsets.push(fld.offset());
entry->_entries++;
entry->_instance_size += DumperSupport::sig2size(sig);
}
}
if (_ptrs->number_of_entries() >= CACHE_TOP) {
// We do not track the individual hit rates for table entries.
// Purge the entire table, and let the cache catch up with new
// distribution.
unlink_all(_ptrs);
}
_ptrs->put(ik, entry);
} else {
entry = *from_cache;
}
// Remember for single-slot cache.
_last_ik = ik;
_last_entry = entry;
return entry;
}
DumperClassCacheTable() : _ptrs(new (mtServiceability) PtrTable), _last_ik(nullptr), _last_entry(nullptr) {}
~DumperClassCacheTable() {
unlink_all(_ptrs);
delete _ptrs;
}
};
// write a header of the given type // write a header of the given type
void DumperSupport:: write_header(AbstractDumpWriter* writer, hprofTag tag, u4 len) { void DumperSupport:: write_header(AbstractDumpWriter* writer, hprofTag tag, u4 len) {
writer->write_u1(tag); writer->write_u1(tag);
@ -931,16 +1033,18 @@ void DumperSupport::dump_field_value(AbstractDumpWriter* writer, char type, oop
} }
// returns the size of the instance of the given class // returns the size of the instance of the given class
u4 DumperSupport::instance_size(Klass* k) { u4 DumperSupport::instance_size(InstanceKlass* ik, DumperClassCacheTableEntry* class_cache_entry) {
InstanceKlass* ik = InstanceKlass::cast(k); if (class_cache_entry != nullptr) {
u4 size = 0; return class_cache_entry->instance_size();
} else {
for (HierarchicalFieldStream<JavaFieldStream> fld(ik); !fld.done(); fld.next()) { u4 size = 0;
if (!fld.access_flags().is_static()) { for (HierarchicalFieldStream<JavaFieldStream> fld(ik); !fld.done(); fld.next()) {
size += sig2size(fld.signature()); if (!fld.access_flags().is_static()) {
size += sig2size(fld.signature());
}
} }
return size;
} }
return size;
} }
u4 DumperSupport::get_static_fields_size(InstanceKlass* ik, u2& field_count) { u4 DumperSupport::get_static_fields_size(InstanceKlass* ik, u2& field_count) {
@ -1012,14 +1116,10 @@ void DumperSupport::dump_static_fields(AbstractDumpWriter* writer, Klass* k) {
} }
// dump the raw values of the instance fields of the given object // dump the raw values of the instance fields of the given object
void DumperSupport::dump_instance_fields(AbstractDumpWriter* writer, oop o) { void DumperSupport::dump_instance_fields(AbstractDumpWriter* writer, oop o, DumperClassCacheTableEntry* class_cache_entry) {
InstanceKlass* ik = InstanceKlass::cast(o->klass()); assert(class_cache_entry != nullptr, "Pre-condition: must be provided");
for (int idx = 0; idx < class_cache_entry->field_count(); idx++) {
for (HierarchicalFieldStream<JavaFieldStream> fld(ik); !fld.done(); fld.next()) { dump_field_value(writer, class_cache_entry->sig_start(idx), o, class_cache_entry->offset(idx));
if (!fld.access_flags().is_static()) {
Symbol* sig = fld.signature();
dump_field_value(writer, sig->char_at(0), o, fld.offset());
}
} }
} }
@ -1050,9 +1150,12 @@ void DumperSupport::dump_instance_field_descriptors(AbstractDumpWriter* writer,
} }
// creates HPROF_GC_INSTANCE_DUMP record for the given object // creates HPROF_GC_INSTANCE_DUMP record for the given object
void DumperSupport::dump_instance(AbstractDumpWriter* writer, oop o) { void DumperSupport::dump_instance(AbstractDumpWriter* writer, oop o, DumperClassCacheTable* class_cache) {
InstanceKlass* ik = InstanceKlass::cast(o->klass()); InstanceKlass* ik = InstanceKlass::cast(o->klass());
u4 is = instance_size(ik);
DumperClassCacheTableEntry* cache_entry = class_cache->lookup_or_create(ik);
u4 is = instance_size(ik, cache_entry);
u4 size = 1 + sizeof(address) + 4 + sizeof(address) + 4 + is; u4 size = 1 + sizeof(address) + 4 + sizeof(address) + 4 + is;
writer->start_sub_record(HPROF_GC_INSTANCE_DUMP, size); writer->start_sub_record(HPROF_GC_INSTANCE_DUMP, size);
@ -1066,7 +1169,7 @@ void DumperSupport::dump_instance(AbstractDumpWriter* writer, oop o) {
writer->write_u4(is); writer->write_u4(is);
// field values // field values
dump_instance_fields(writer, o); dump_instance_fields(writer, o, cache_entry);
writer->end_sub_record(); writer->end_sub_record();
} }
@ -1763,6 +1866,8 @@ class HeapObjectDumper : public ObjectClosure {
AbstractDumpWriter* _writer; AbstractDumpWriter* _writer;
AbstractDumpWriter* writer() { return _writer; } AbstractDumpWriter* writer() { return _writer; }
DumperClassCacheTable _class_cache;
public: public:
HeapObjectDumper(AbstractDumpWriter* writer) { HeapObjectDumper(AbstractDumpWriter* writer) {
_writer = writer; _writer = writer;
@ -1787,7 +1892,7 @@ void HeapObjectDumper::do_object(oop o) {
if (o->is_instance()) { if (o->is_instance()) {
// create a HPROF_GC_INSTANCE record for each object // create a HPROF_GC_INSTANCE record for each object
DumperSupport::dump_instance(writer(), o); DumperSupport::dump_instance(writer(), o, &_class_cache);
} else if (o->is_objArray()) { } else if (o->is_objArray()) {
// create a HPROF_GC_OBJ_ARRAY_DUMP record for each object array // create a HPROF_GC_OBJ_ARRAY_DUMP record for each object array
DumperSupport::dump_object_array(writer(), objArrayOop(o)); DumperSupport::dump_object_array(writer(), objArrayOop(o));
@ -2338,6 +2443,7 @@ void VM_HeapDumper::work(uint worker_id) {
if (!is_parallel_dump()) { if (!is_parallel_dump()) {
assert(is_vm_dumper(worker_id), "must be"); assert(is_vm_dumper(worker_id), "must be");
// == Serial dump // == Serial dump
ResourceMark rm;
TraceTime timer("Dump heap objects", TRACETIME_LOG(Info, heapdump)); TraceTime timer("Dump heap objects", TRACETIME_LOG(Info, heapdump));
HeapObjectDumper obj_dumper(writer()); HeapObjectDumper obj_dumper(writer());
Universe::heap()->object_iterate(&obj_dumper); Universe::heap()->object_iterate(&obj_dumper);