From 03db82818b905f21cb5ad1d56a687e238b4a6e33 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Mon, 13 Nov 2023 11:51:38 +0000 Subject: [PATCH] 8319650: Improve heap dump performance with class metadata caching Reviewed-by: simonis, coleenp, yyang --- src/hotspot/share/services/heapDumper.cpp | 154 ++++++++++++++++++---- 1 file changed, 130 insertions(+), 24 deletions(-) diff --git a/src/hotspot/share/services/heapDumper.cpp b/src/hotspot/share/services/heapDumper.cpp index 9fc7407ed8e..027974032cd 100644 --- a/src/hotspot/share/services/heapDumper.cpp +++ b/src/hotspot/share/services/heapDumper.cpp @@ -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 { public: @@ -738,7 +740,7 @@ class DumperSupport : AllStatic { static u4 sig2size(Symbol* sig); // 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 static void dump_float(AbstractDumpWriter* writer, jfloat f); @@ -751,13 +753,13 @@ class DumperSupport : AllStatic { // dumps static fields of the given class static void dump_static_fields(AbstractDumpWriter* writer, Klass* k); // 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 static u2 get_instance_fields_count(InstanceKlass* ik); // dumps the definition of the instance fields for a given class static void dump_instance_field_descriptors(AbstractDumpWriter* writer, Klass* k); // 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 static void dump_instance_class(AbstractDumpWriter* writer, Klass* k); // 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 { + friend class DumperClassCacheTable; +private: + GrowableArray _sigs_start; + GrowableArray _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 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 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 void DumperSupport:: write_header(AbstractDumpWriter* writer, hprofTag tag, u4 len) { 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 -u4 DumperSupport::instance_size(Klass* k) { - InstanceKlass* ik = InstanceKlass::cast(k); - u4 size = 0; - - for (HierarchicalFieldStream fld(ik); !fld.done(); fld.next()) { - if (!fld.access_flags().is_static()) { - size += sig2size(fld.signature()); +u4 DumperSupport::instance_size(InstanceKlass* ik, DumperClassCacheTableEntry* class_cache_entry) { + if (class_cache_entry != nullptr) { + return class_cache_entry->instance_size(); + } else { + u4 size = 0; + for (HierarchicalFieldStream fld(ik); !fld.done(); fld.next()) { + 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) { @@ -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 -void DumperSupport::dump_instance_fields(AbstractDumpWriter* writer, oop o) { - InstanceKlass* ik = InstanceKlass::cast(o->klass()); - - for (HierarchicalFieldStream fld(ik); !fld.done(); fld.next()) { - if (!fld.access_flags().is_static()) { - Symbol* sig = fld.signature(); - dump_field_value(writer, sig->char_at(0), o, fld.offset()); - } +void DumperSupport::dump_instance_fields(AbstractDumpWriter* writer, oop o, DumperClassCacheTableEntry* class_cache_entry) { + assert(class_cache_entry != nullptr, "Pre-condition: must be provided"); + for (int idx = 0; idx < class_cache_entry->field_count(); idx++) { + dump_field_value(writer, class_cache_entry->sig_start(idx), o, class_cache_entry->offset(idx)); } } @@ -1050,9 +1150,12 @@ void DumperSupport::dump_instance_field_descriptors(AbstractDumpWriter* writer, } // 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()); - 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; 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); // field values - dump_instance_fields(writer, o); + dump_instance_fields(writer, o, cache_entry); writer->end_sub_record(); } @@ -1763,6 +1866,8 @@ class HeapObjectDumper : public ObjectClosure { AbstractDumpWriter* _writer; AbstractDumpWriter* writer() { return _writer; } + DumperClassCacheTable _class_cache; + public: HeapObjectDumper(AbstractDumpWriter* writer) { _writer = writer; @@ -1787,7 +1892,7 @@ void HeapObjectDumper::do_object(oop o) { if (o->is_instance()) { // 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()) { // create a HPROF_GC_OBJ_ARRAY_DUMP record for each object array DumperSupport::dump_object_array(writer(), objArrayOop(o)); @@ -2338,6 +2443,7 @@ void VM_HeapDumper::work(uint worker_id) { if (!is_parallel_dump()) { assert(is_vm_dumper(worker_id), "must be"); // == Serial dump + ResourceMark rm; TraceTime timer("Dump heap objects", TRACETIME_LOG(Info, heapdump)); HeapObjectDumper obj_dumper(writer()); Universe::heap()->object_iterate(&obj_dumper);