Skip to content

Commit

Permalink
8319650: Improve heap dump performance with class metadata caching
Browse files Browse the repository at this point in the history
Reviewed-by: phh
Backport-of: 03db82818b905f21cb5ad1d56a687e238b4a6e33
  • Loading branch information
shipilev committed Apr 9, 2024
1 parent b9ca253 commit d5f44dc
Showing 1 changed file with 133 additions and 24 deletions.
157 changes: 133 additions & 24 deletions src/hotspot/share/services/heapDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -883,8 +883,10 @@ class ParDumpWriter : public AbstractDumpWriter {

Monitor* ParDumpWriter::_lock = nullptr;

// 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:

Expand All @@ -899,7 +901,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);
Expand All @@ -912,13 +914,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
Expand Down Expand Up @@ -948,6 +950,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
void DumperSupport:: write_header(AbstractDumpWriter* writer, hprofTag tag, u4 len) {
writer->write_u1(tag);
Expand Down Expand Up @@ -1092,16 +1194,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<JavaFieldStream> 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<JavaFieldStream> 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) {
Expand Down Expand Up @@ -1173,14 +1277,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<JavaFieldStream> 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));
}
}

Expand Down Expand Up @@ -1211,9 +1311,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);
Expand All @@ -1227,7 +1330,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();
}
Expand Down Expand Up @@ -1690,6 +1793,9 @@ class HeapObjectDumper : public ObjectClosure {

AbstractDumpWriter* writer() { return _writer; }
bool is_large(oop o);

DumperClassCacheTable _class_cache;

public:
HeapObjectDumper(AbstractDumpWriter* writer, HeapDumpLargeObjectList* list = nullptr) {
_writer = writer;
Expand Down Expand Up @@ -1722,7 +1828,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));
Expand Down Expand Up @@ -2278,6 +2384,7 @@ void VM_HeapDumper::work(uint worker_id) {
// The HPROF_GC_CLASS_DUMP and HPROF_GC_INSTANCE_DUMP are the vast bulk
// of the heap dump.
if (_num_dumper_threads <= 1) {
ResourceMark rm;
HeapObjectDumper obj_dumper(writer());
Universe::heap()->object_iterate(&obj_dumper);
} else {
Expand All @@ -2294,6 +2401,7 @@ void VM_HeapDumper::work(uint worker_id) {
{
ParDumpWriter pw(writer());
{
ResourceMark rm;
HeapObjectDumper obj_dumper(&pw, _large_object_list);
_poi->object_iterate(&obj_dumper, worker_id);
}
Expand All @@ -2314,6 +2422,7 @@ void VM_HeapDumper::work(uint worker_id) {

assert(get_worker_type(worker_id) == VMDumperType, "Heap dumper must be VMDumper");
// Use writer() rather than ParDumpWriter to avoid memory consumption.
ResourceMark rm;
HeapObjectDumper obj_dumper(writer());
dump_large_objects(&obj_dumper);
// Writes the HPROF_HEAP_DUMP_END record.
Expand Down

1 comment on commit d5f44dc

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.