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: simonis, coleenp, yyang
  • Loading branch information
shipilev committed Nov 13, 2023
1 parent b41b00a commit 03db828
Showing 1 changed file with 130 additions and 24 deletions.
154 changes: 130 additions & 24 deletions src/hotspot/share/services/heapDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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);
Expand All @@ -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
Expand Down Expand Up @@ -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
void DumperSupport:: write_header(AbstractDumpWriter* writer, hprofTag tag, u4 len) {
writer->write_u1(tag);
Expand Down Expand Up @@ -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<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 @@ -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<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 @@ -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);
Expand All @@ -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();
}
Expand Down Expand Up @@ -1763,6 +1866,8 @@ class HeapObjectDumper : public ObjectClosure {
AbstractDumpWriter* _writer;
AbstractDumpWriter* writer() { return _writer; }

DumperClassCacheTable _class_cache;

public:
HeapObjectDumper(AbstractDumpWriter* writer) {
_writer = writer;
Expand All @@ -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));
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 03db828

Please sign in to comment.