8316691: Heap dump: separate stack traces for mounted virtual threads
Reviewed-by: lmesnik, sspitsyn
This commit is contained in:
parent
33591a30d2
commit
2b8276aa52
@ -43,6 +43,7 @@
|
|||||||
#include "oops/objArrayOop.inline.hpp"
|
#include "oops/objArrayOop.inline.hpp"
|
||||||
#include "oops/oop.inline.hpp"
|
#include "oops/oop.inline.hpp"
|
||||||
#include "oops/typeArrayOop.inline.hpp"
|
#include "oops/typeArrayOop.inline.hpp"
|
||||||
|
#include "runtime/continuationWrapper.inline.hpp"
|
||||||
#include "runtime/frame.inline.hpp"
|
#include "runtime/frame.inline.hpp"
|
||||||
#include "runtime/handles.inline.hpp"
|
#include "runtime/handles.inline.hpp"
|
||||||
#include "runtime/javaCalls.hpp"
|
#include "runtime/javaCalls.hpp"
|
||||||
@ -1385,7 +1386,6 @@ class JNILocalsDumper : public OopClosure {
|
|||||||
void do_oop(narrowOop* obj_p) { ShouldNotReachHere(); }
|
void do_oop(narrowOop* obj_p) { ShouldNotReachHere(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
void JNILocalsDumper::do_oop(oop* obj_p) {
|
void JNILocalsDumper::do_oop(oop* obj_p) {
|
||||||
// ignore null handles
|
// ignore null handles
|
||||||
oop o = *obj_p;
|
oop o = *obj_p;
|
||||||
@ -1451,6 +1451,310 @@ class StickyClassDumper : public KlassClosure {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Support class used to generate HPROF_GC_ROOT_JAVA_FRAME records.
|
||||||
|
|
||||||
|
class JavaStackRefDumper : public StackObj {
|
||||||
|
private:
|
||||||
|
AbstractDumpWriter* _writer;
|
||||||
|
u4 _thread_serial_num;
|
||||||
|
int _frame_num;
|
||||||
|
AbstractDumpWriter* writer() const { return _writer; }
|
||||||
|
public:
|
||||||
|
JavaStackRefDumper(AbstractDumpWriter* writer, u4 thread_serial_num)
|
||||||
|
: _writer(writer), _thread_serial_num(thread_serial_num), _frame_num(-1) // default - empty stack
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_frame_number(int n) { _frame_num = n; }
|
||||||
|
|
||||||
|
void dump_java_stack_refs(StackValueCollection* values);
|
||||||
|
};
|
||||||
|
|
||||||
|
void JavaStackRefDumper::dump_java_stack_refs(StackValueCollection* values) {
|
||||||
|
for (int index = 0; index < values->size(); index++) {
|
||||||
|
if (values->at(index)->type() == T_OBJECT) {
|
||||||
|
oop o = values->obj_at(index)();
|
||||||
|
if (o != nullptr) {
|
||||||
|
u4 size = 1 + sizeof(address) + 4 + 4;
|
||||||
|
writer()->start_sub_record(HPROF_GC_ROOT_JAVA_FRAME, size);
|
||||||
|
writer()->write_objectID(o);
|
||||||
|
writer()->write_u4(_thread_serial_num);
|
||||||
|
writer()->write_u4((u4)_frame_num);
|
||||||
|
writer()->end_sub_record();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Class to collect, store and dump thread-related data:
|
||||||
|
// - HPROF_TRACE and HPROF_FRAME records;
|
||||||
|
// - HPROF_GC_ROOT_THREAD_OBJ/HPROF_GC_ROOT_JAVA_FRAME/HPROF_GC_ROOT_JNI_LOCAL subrecords.
|
||||||
|
class ThreadDumper : public CHeapObj<mtInternal> {
|
||||||
|
public:
|
||||||
|
enum class ThreadType { Platform, MountedVirtual, UnmountedVirtual };
|
||||||
|
|
||||||
|
private:
|
||||||
|
ThreadType _thread_type;
|
||||||
|
JavaThread* _java_thread;
|
||||||
|
oop _thread_oop;
|
||||||
|
|
||||||
|
GrowableArray<StackFrameInfo*>* _frames;
|
||||||
|
// non-null if the thread is OOM thread
|
||||||
|
Method* _oome_constructor;
|
||||||
|
int _thread_serial_num;
|
||||||
|
int _start_frame_serial_num;
|
||||||
|
|
||||||
|
vframe* get_top_frame() const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static bool should_dump_pthread(JavaThread* thread) {
|
||||||
|
return thread->threadObj() != nullptr && !thread->is_exiting() && !thread->is_hidden_from_external_view();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool should_dump_vthread(oop vt) {
|
||||||
|
return java_lang_VirtualThread::state(vt) != java_lang_VirtualThread::NEW
|
||||||
|
&& java_lang_VirtualThread::state(vt) != java_lang_VirtualThread::TERMINATED;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadDumper(ThreadType thread_type, JavaThread* java_thread, oop thread_oop);
|
||||||
|
|
||||||
|
// affects frame_count
|
||||||
|
void add_oom_frame(Method* oome_constructor) {
|
||||||
|
assert(_start_frame_serial_num == 0, "add_oom_frame cannot be called after init_serial_nums");
|
||||||
|
_oome_constructor = oome_constructor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_serial_nums(volatile int* thread_counter, volatile int* frame_counter) {
|
||||||
|
assert(_start_frame_serial_num == 0, "already initialized");
|
||||||
|
_thread_serial_num = Atomic::fetch_then_add(thread_counter, 1);
|
||||||
|
_start_frame_serial_num = Atomic::fetch_then_add(frame_counter, frame_count());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool oom_thread() const {
|
||||||
|
return _oome_constructor != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int frame_count() const {
|
||||||
|
return _frames->length() + (oom_thread() ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
u4 thread_serial_num() const {
|
||||||
|
return (u4)_thread_serial_num;
|
||||||
|
}
|
||||||
|
|
||||||
|
u4 stack_trace_serial_num() const {
|
||||||
|
return (u4)(_thread_serial_num + STACK_TRACE_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// writes HPROF_TRACE and HPROF_FRAME records
|
||||||
|
// returns number of dumped frames
|
||||||
|
void dump_stack_traces(AbstractDumpWriter* writer, GrowableArray<Klass*>* klass_map);
|
||||||
|
|
||||||
|
// writes HPROF_GC_ROOT_THREAD_OBJ subrecord
|
||||||
|
void dump_thread_obj(AbstractDumpWriter* writer);
|
||||||
|
|
||||||
|
// Walk the stack of the thread.
|
||||||
|
// Dumps a HPROF_GC_ROOT_JAVA_FRAME subrecord for each local
|
||||||
|
// Dumps a HPROF_GC_ROOT_JNI_LOCAL subrecord for each JNI local
|
||||||
|
void dump_stack_refs(AbstractDumpWriter* writer);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
ThreadDumper::ThreadDumper(ThreadType thread_type, JavaThread* java_thread, oop thread_oop)
|
||||||
|
: _thread_type(thread_type), _java_thread(java_thread), _thread_oop(thread_oop),
|
||||||
|
_oome_constructor(nullptr),
|
||||||
|
_thread_serial_num(0), _start_frame_serial_num(0)
|
||||||
|
{
|
||||||
|
// sanity checks
|
||||||
|
if (_thread_type == ThreadType::UnmountedVirtual) {
|
||||||
|
assert(_java_thread == nullptr, "sanity");
|
||||||
|
assert(_thread_oop != nullptr, "sanity");
|
||||||
|
} else {
|
||||||
|
assert(_java_thread != nullptr, "sanity");
|
||||||
|
assert(_thread_oop != nullptr, "sanity");
|
||||||
|
}
|
||||||
|
|
||||||
|
_frames = new (mtServiceability) GrowableArray<StackFrameInfo*>(10, mtServiceability);
|
||||||
|
bool stop_at_vthread_entry = _thread_type == ThreadType::MountedVirtual;
|
||||||
|
|
||||||
|
// vframes are resource allocated
|
||||||
|
Thread* current_thread = Thread::current();
|
||||||
|
ResourceMark rm(current_thread);
|
||||||
|
HandleMark hm(current_thread);
|
||||||
|
|
||||||
|
for (vframe* vf = get_top_frame(); vf != nullptr; vf = vf->sender()) {
|
||||||
|
if (stop_at_vthread_entry && vf->is_vthread_entry()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (vf->is_java_frame()) {
|
||||||
|
javaVFrame* jvf = javaVFrame::cast(vf);
|
||||||
|
_frames->append(new StackFrameInfo(jvf, false));
|
||||||
|
} else {
|
||||||
|
// ignore non-Java frames
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadDumper::dump_stack_traces(AbstractDumpWriter* writer, GrowableArray<Klass*>* klass_map) {
|
||||||
|
assert(_thread_serial_num != 0 && _start_frame_serial_num != 0, "serial_nums are not initialized");
|
||||||
|
|
||||||
|
// write HPROF_FRAME records for this thread's stack trace
|
||||||
|
int depth = _frames->length();
|
||||||
|
int frame_serial_num = _start_frame_serial_num;
|
||||||
|
|
||||||
|
if (oom_thread()) {
|
||||||
|
// OOM thread
|
||||||
|
// write fake frame that makes it look like the thread, which caused OOME,
|
||||||
|
// is in the OutOfMemoryError zero-parameter constructor
|
||||||
|
int oome_serial_num = klass_map->find(_oome_constructor->method_holder());
|
||||||
|
// the class serial number starts from 1
|
||||||
|
assert(oome_serial_num > 0, "OutOfMemoryError class not found");
|
||||||
|
DumperSupport::dump_stack_frame(writer, ++frame_serial_num, oome_serial_num, _oome_constructor, 0);
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < _frames->length(); j++) {
|
||||||
|
StackFrameInfo* frame = _frames->at(j);
|
||||||
|
Method* m = frame->method();
|
||||||
|
int class_serial_num = klass_map->find(m->method_holder());
|
||||||
|
// the class serial number starts from 1
|
||||||
|
assert(class_serial_num > 0, "class not found");
|
||||||
|
DumperSupport::dump_stack_frame(writer, ++frame_serial_num, class_serial_num, m, frame->bci());
|
||||||
|
}
|
||||||
|
|
||||||
|
// write HPROF_TRACE record for the thread
|
||||||
|
DumperSupport::write_header(writer, HPROF_TRACE, checked_cast<u4>(3 * sizeof(u4) + depth * oopSize));
|
||||||
|
writer->write_u4(stack_trace_serial_num()); // stack trace serial number
|
||||||
|
writer->write_u4(thread_serial_num()); // thread serial number
|
||||||
|
writer->write_u4((u4)depth); // frame count (including oom frame)
|
||||||
|
for (int j = 1; j <= depth; j++) {
|
||||||
|
writer->write_id(_start_frame_serial_num + j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadDumper::dump_thread_obj(AbstractDumpWriter * writer) {
|
||||||
|
assert(_thread_serial_num != 0 && _start_frame_serial_num != 0, "serial_num is not initialized");
|
||||||
|
|
||||||
|
u4 size = 1 + sizeof(address) + 4 + 4;
|
||||||
|
writer->start_sub_record(HPROF_GC_ROOT_THREAD_OBJ, size);
|
||||||
|
writer->write_objectID(_thread_oop);
|
||||||
|
writer->write_u4(thread_serial_num()); // thread serial number
|
||||||
|
writer->write_u4(stack_trace_serial_num()); // stack trace serial number
|
||||||
|
writer->end_sub_record();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadDumper::dump_stack_refs(AbstractDumpWriter * writer) {
|
||||||
|
assert(_thread_serial_num != 0 && _start_frame_serial_num != 0, "serial_num is not initialized");
|
||||||
|
|
||||||
|
JNILocalsDumper blk(writer, thread_serial_num());
|
||||||
|
if (_thread_type == ThreadType::Platform) {
|
||||||
|
if (!_java_thread->has_last_Java_frame()) {
|
||||||
|
// no last java frame but there may be JNI locals
|
||||||
|
_java_thread->active_handles()->oops_do(&blk);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JavaStackRefDumper java_ref_dumper(writer, thread_serial_num());
|
||||||
|
|
||||||
|
// vframes are resource allocated
|
||||||
|
Thread* current_thread = Thread::current();
|
||||||
|
ResourceMark rm(current_thread);
|
||||||
|
HandleMark hm(current_thread);
|
||||||
|
|
||||||
|
bool stopAtVthreadEntry = _thread_type == ThreadType::MountedVirtual;
|
||||||
|
frame* last_entry_frame = nullptr;
|
||||||
|
bool is_top_frame = true;
|
||||||
|
int depth = 0;
|
||||||
|
if (oom_thread()) {
|
||||||
|
depth++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (vframe* vf = get_top_frame(); vf != nullptr; vf = vf->sender()) {
|
||||||
|
if (stopAtVthreadEntry && vf->is_vthread_entry()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vf->is_java_frame()) {
|
||||||
|
javaVFrame* jvf = javaVFrame::cast(vf);
|
||||||
|
if (!(jvf->method()->is_native())) {
|
||||||
|
java_ref_dumper.set_frame_number(depth);
|
||||||
|
java_ref_dumper.dump_java_stack_refs(jvf->locals());
|
||||||
|
java_ref_dumper.dump_java_stack_refs(jvf->expressions());
|
||||||
|
} else {
|
||||||
|
// native frame
|
||||||
|
blk.set_frame_number(depth);
|
||||||
|
if (is_top_frame) {
|
||||||
|
// JNI locals for the top frame.
|
||||||
|
assert(_java_thread != nullptr, "impossible for unmounted vthread");
|
||||||
|
_java_thread->active_handles()->oops_do(&blk);
|
||||||
|
} else {
|
||||||
|
if (last_entry_frame != nullptr) {
|
||||||
|
// JNI locals for the entry frame
|
||||||
|
assert(last_entry_frame->is_entry_frame(), "checking");
|
||||||
|
last_entry_frame->entry_frame_call_wrapper()->handles()->oops_do(&blk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last_entry_frame = nullptr;
|
||||||
|
// increment only for Java frames
|
||||||
|
depth++;
|
||||||
|
} else {
|
||||||
|
// externalVFrame - for an entry frame then we report the JNI locals
|
||||||
|
// when we find the corresponding javaVFrame
|
||||||
|
frame* fr = vf->frame_pointer();
|
||||||
|
assert(fr != nullptr, "sanity check");
|
||||||
|
if (fr->is_entry_frame()) {
|
||||||
|
last_entry_frame = fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is_top_frame = false;
|
||||||
|
}
|
||||||
|
assert(depth == frame_count(), "total number of Java frames not matched");
|
||||||
|
}
|
||||||
|
|
||||||
|
vframe* ThreadDumper::get_top_frame() const {
|
||||||
|
if (_thread_type == ThreadType::UnmountedVirtual) {
|
||||||
|
ContinuationWrapper cont(java_lang_VirtualThread::continuation(_thread_oop));
|
||||||
|
if (cont.is_empty()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
assert(!cont.is_mounted(), "sanity check");
|
||||||
|
stackChunkOop chunk = cont.last_nonempty_chunk();
|
||||||
|
if (chunk == nullptr || chunk->is_empty()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterMap reg_map(cont.continuation(), RegisterMap::UpdateMap::include);
|
||||||
|
frame fr = chunk->top_frame(®_map);
|
||||||
|
vframe* vf = vframe::new_vframe(&fr, ®_map, nullptr); // don't need JavaThread
|
||||||
|
return vf;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterMap reg_map(_java_thread,
|
||||||
|
RegisterMap::UpdateMap::include,
|
||||||
|
RegisterMap::ProcessFrames::include,
|
||||||
|
RegisterMap::WalkContinuation::skip);
|
||||||
|
switch (_thread_type) {
|
||||||
|
case ThreadType::Platform:
|
||||||
|
if (!_java_thread->has_last_Java_frame()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return _java_thread->is_vthread_mounted()
|
||||||
|
? _java_thread->carrier_last_java_vframe(®_map)
|
||||||
|
: _java_thread->platform_thread_last_java_vframe(®_map);
|
||||||
|
|
||||||
|
case ThreadType::MountedVirtual:
|
||||||
|
return _java_thread->last_java_vframe(®_map);
|
||||||
|
|
||||||
|
default: // make compilers happy
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ShouldNotReachHere();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class VM_HeapDumper;
|
class VM_HeapDumper;
|
||||||
|
|
||||||
// Support class using when iterating over the heap.
|
// Support class using when iterating over the heap.
|
||||||
@ -1683,8 +1987,12 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask {
|
|||||||
Method* _oome_constructor;
|
Method* _oome_constructor;
|
||||||
bool _gc_before_heap_dump;
|
bool _gc_before_heap_dump;
|
||||||
GrowableArray<Klass*>* _klass_map;
|
GrowableArray<Klass*>* _klass_map;
|
||||||
ThreadStackTrace** _stack_traces;
|
|
||||||
int _num_threads;
|
ThreadDumper** _thread_dumpers; // platform, carrier and mounted virtual threads
|
||||||
|
int _thread_dumpers_count;
|
||||||
|
volatile int _thread_serial_num;
|
||||||
|
volatile int _frame_serial_num;
|
||||||
|
|
||||||
volatile int _dump_seq;
|
volatile int _dump_seq;
|
||||||
// parallel heap dump support
|
// parallel heap dump support
|
||||||
uint _num_dumper_threads;
|
uint _num_dumper_threads;
|
||||||
@ -1721,15 +2029,18 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask {
|
|||||||
// writes a HPROF_GC_CLASS_DUMP record for the given class
|
// writes a HPROF_GC_CLASS_DUMP record for the given class
|
||||||
static void do_class_dump(Klass* k);
|
static void do_class_dump(Klass* k);
|
||||||
|
|
||||||
// HPROF_GC_ROOT_THREAD_OBJ records
|
// HPROF_GC_ROOT_THREAD_OBJ records for platform and mounted virtual threads
|
||||||
int do_thread(JavaThread* thread, u4 thread_serial_num);
|
void dump_threads();
|
||||||
void do_threads();
|
|
||||||
|
|
||||||
void add_class_serial_number(Klass* k, int serial_num) {
|
void add_class_serial_number(Klass* k, int serial_num) {
|
||||||
_klass_map->at_put_grow(serial_num, k);
|
_klass_map->at_put_grow(serial_num, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
// HPROF_TRACE and HPROF_FRAME records
|
bool is_oom_thread(JavaThread* thread) const {
|
||||||
|
return thread == _oome_thread && _oome_constructor != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HPROF_TRACE and HPROF_FRAME records for platform and mounted virtual threads
|
||||||
void dump_stack_traces();
|
void dump_stack_traces();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -1742,8 +2053,12 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask {
|
|||||||
_local_writer = writer;
|
_local_writer = writer;
|
||||||
_gc_before_heap_dump = gc_before_heap_dump;
|
_gc_before_heap_dump = gc_before_heap_dump;
|
||||||
_klass_map = new (mtServiceability) GrowableArray<Klass*>(INITIAL_CLASS_COUNT, mtServiceability);
|
_klass_map = new (mtServiceability) GrowableArray<Klass*>(INITIAL_CLASS_COUNT, mtServiceability);
|
||||||
_stack_traces = nullptr;
|
|
||||||
_num_threads = 0;
|
_thread_dumpers = nullptr;
|
||||||
|
_thread_dumpers_count = 0;
|
||||||
|
_thread_serial_num = 1;
|
||||||
|
_frame_serial_num = 1;
|
||||||
|
|
||||||
_dump_seq = 0;
|
_dump_seq = 0;
|
||||||
_num_dumper_threads = num_dump_threads;
|
_num_dumper_threads = num_dump_threads;
|
||||||
_dumper_controller = nullptr;
|
_dumper_controller = nullptr;
|
||||||
@ -1763,12 +2078,13 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
~VM_HeapDumper() {
|
~VM_HeapDumper() {
|
||||||
if (_stack_traces != nullptr) {
|
if (_thread_dumpers != nullptr) {
|
||||||
for (int i=0; i < _num_threads; i++) {
|
for (int i = 0; i < _thread_dumpers_count; i++) {
|
||||||
delete _stack_traces[i];
|
delete _thread_dumpers[i];
|
||||||
}
|
}
|
||||||
FREE_C_HEAP_ARRAY(ThreadStackTrace*, _stack_traces);
|
FREE_C_HEAP_ARRAY(ThreadDumper*, _thread_dumpers);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_dumper_controller != nullptr) {
|
if (_dumper_controller != nullptr) {
|
||||||
delete _dumper_controller;
|
delete _dumper_controller;
|
||||||
_dumper_controller = nullptr;
|
_dumper_controller = nullptr;
|
||||||
@ -1835,126 +2151,12 @@ void VM_HeapDumper::do_class_dump(Klass* k) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the stack of the given thread.
|
// Write a HPROF_GC_ROOT_THREAD_OBJ record for platform/carrier and mounted virtual threads.
|
||||||
// Dumps a HPROF_GC_ROOT_JAVA_FRAME record for each local
|
// Then walk the stack so that locals and JNI locals are dumped.
|
||||||
// Dumps a HPROF_GC_ROOT_JNI_LOCAL record for each JNI local
|
void VM_HeapDumper::dump_threads() {
|
||||||
//
|
for (int i = 0; i < _thread_dumpers_count; i++) {
|
||||||
// It returns the number of Java frames in this thread stack
|
_thread_dumpers[i]->dump_thread_obj(writer());
|
||||||
int VM_HeapDumper::do_thread(JavaThread* java_thread, u4 thread_serial_num) {
|
_thread_dumpers[i]->dump_stack_refs(writer());
|
||||||
JNILocalsDumper blk(writer(), thread_serial_num);
|
|
||||||
|
|
||||||
oop threadObj = java_thread->threadObj();
|
|
||||||
assert(threadObj != nullptr, "sanity check");
|
|
||||||
|
|
||||||
int stack_depth = 0;
|
|
||||||
if (java_thread->has_last_Java_frame()) {
|
|
||||||
|
|
||||||
// vframes are resource allocated
|
|
||||||
Thread* current_thread = Thread::current();
|
|
||||||
ResourceMark rm(current_thread);
|
|
||||||
HandleMark hm(current_thread);
|
|
||||||
|
|
||||||
RegisterMap reg_map(java_thread,
|
|
||||||
RegisterMap::UpdateMap::include,
|
|
||||||
RegisterMap::ProcessFrames::include,
|
|
||||||
RegisterMap::WalkContinuation::skip);
|
|
||||||
frame f = java_thread->last_frame();
|
|
||||||
vframe* vf = vframe::new_vframe(&f, ®_map, java_thread);
|
|
||||||
frame* last_entry_frame = nullptr;
|
|
||||||
int extra_frames = 0;
|
|
||||||
|
|
||||||
if (java_thread == _oome_thread && _oome_constructor != nullptr) {
|
|
||||||
extra_frames++;
|
|
||||||
}
|
|
||||||
while (vf != nullptr) {
|
|
||||||
blk.set_frame_number(stack_depth);
|
|
||||||
if (vf->is_java_frame()) {
|
|
||||||
|
|
||||||
// java frame (interpreted, compiled, ...)
|
|
||||||
javaVFrame *jvf = javaVFrame::cast(vf);
|
|
||||||
if (!(jvf->method()->is_native())) {
|
|
||||||
StackValueCollection* locals = jvf->locals();
|
|
||||||
for (int slot=0; slot<locals->size(); slot++) {
|
|
||||||
if (locals->at(slot)->type() == T_OBJECT) {
|
|
||||||
oop o = locals->obj_at(slot)();
|
|
||||||
|
|
||||||
if (o != nullptr) {
|
|
||||||
u4 size = 1 + sizeof(address) + 4 + 4;
|
|
||||||
writer()->start_sub_record(HPROF_GC_ROOT_JAVA_FRAME, size);
|
|
||||||
writer()->write_objectID(o);
|
|
||||||
writer()->write_u4(thread_serial_num);
|
|
||||||
writer()->write_u4((u4) (stack_depth + extra_frames));
|
|
||||||
writer()->end_sub_record();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StackValueCollection *exprs = jvf->expressions();
|
|
||||||
for(int index = 0; index < exprs->size(); index++) {
|
|
||||||
if (exprs->at(index)->type() == T_OBJECT) {
|
|
||||||
oop o = exprs->obj_at(index)();
|
|
||||||
if (o != nullptr) {
|
|
||||||
u4 size = 1 + sizeof(address) + 4 + 4;
|
|
||||||
writer()->start_sub_record(HPROF_GC_ROOT_JAVA_FRAME, size);
|
|
||||||
writer()->write_objectID(o);
|
|
||||||
writer()->write_u4(thread_serial_num);
|
|
||||||
writer()->write_u4((u4) (stack_depth + extra_frames));
|
|
||||||
writer()->end_sub_record();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// native frame
|
|
||||||
if (stack_depth == 0) {
|
|
||||||
// JNI locals for the top frame.
|
|
||||||
java_thread->active_handles()->oops_do(&blk);
|
|
||||||
} else {
|
|
||||||
if (last_entry_frame != nullptr) {
|
|
||||||
// JNI locals for the entry frame
|
|
||||||
assert(last_entry_frame->is_entry_frame(), "checking");
|
|
||||||
last_entry_frame->entry_frame_call_wrapper()->handles()->oops_do(&blk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// increment only for Java frames
|
|
||||||
stack_depth++;
|
|
||||||
last_entry_frame = nullptr;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// externalVFrame - if it's an entry frame then report any JNI locals
|
|
||||||
// as roots when we find the corresponding native javaVFrame
|
|
||||||
frame* fr = vf->frame_pointer();
|
|
||||||
assert(fr != nullptr, "sanity check");
|
|
||||||
if (fr->is_entry_frame()) {
|
|
||||||
last_entry_frame = fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vf = vf->sender();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// no last java frame but there may be JNI locals
|
|
||||||
java_thread->active_handles()->oops_do(&blk);
|
|
||||||
}
|
|
||||||
return stack_depth;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// write a HPROF_GC_ROOT_THREAD_OBJ record for each java thread. Then walk
|
|
||||||
// the stack so that locals and JNI locals are dumped.
|
|
||||||
void VM_HeapDumper::do_threads() {
|
|
||||||
for (int i=0; i < _num_threads; i++) {
|
|
||||||
JavaThread* thread = _stack_traces[i]->thread();
|
|
||||||
oop threadObj = thread->threadObj();
|
|
||||||
u4 thread_serial_num = i+1;
|
|
||||||
u4 stack_serial_num = thread_serial_num + STACK_TRACE_ID;
|
|
||||||
u4 size = 1 + sizeof(address) + 4 + 4;
|
|
||||||
writer()->start_sub_record(HPROF_GC_ROOT_THREAD_OBJ, size);
|
|
||||||
writer()->write_objectID(threadObj);
|
|
||||||
writer()->write_u4(thread_serial_num); // thread number
|
|
||||||
writer()->write_u4(stack_serial_num); // stack trace serial number
|
|
||||||
writer()->end_sub_record();
|
|
||||||
int num_frames = do_thread(thread, thread_serial_num);
|
|
||||||
assert(num_frames == _stack_traces[i]->get_stack_depth(),
|
|
||||||
"total number of Java frames not matched");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2100,6 +2302,8 @@ void VM_HeapDumper::work(uint worker_id) {
|
|||||||
// this must be called after _klass_map is built when iterating the classes above.
|
// this must be called after _klass_map is built when iterating the classes above.
|
||||||
dump_stack_traces();
|
dump_stack_traces();
|
||||||
|
|
||||||
|
// HPROF_HEAP_DUMP/HPROF_HEAP_DUMP_SEGMENT starts here
|
||||||
|
|
||||||
// Writes HPROF_GC_CLASS_DUMP records
|
// Writes HPROF_GC_CLASS_DUMP records
|
||||||
{
|
{
|
||||||
LockedClassesDo locked_dump_class(&do_class_dump);
|
LockedClassesDo locked_dump_class(&do_class_dump);
|
||||||
@ -2107,7 +2311,7 @@ void VM_HeapDumper::work(uint worker_id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HPROF_GC_ROOT_THREAD_OBJ + frames + jni locals
|
// HPROF_GC_ROOT_THREAD_OBJ + frames + jni locals
|
||||||
do_threads();
|
dump_threads();
|
||||||
|
|
||||||
// HPROF_GC_ROOT_JNI_GLOBAL
|
// HPROF_GC_ROOT_JNI_GLOBAL
|
||||||
JNIGlobalsDumper jni_dumper(writer());
|
JNIGlobalsDumper jni_dumper(writer());
|
||||||
@ -2163,58 +2367,44 @@ void VM_HeapDumper::work(uint worker_id) {
|
|||||||
|
|
||||||
void VM_HeapDumper::dump_stack_traces() {
|
void VM_HeapDumper::dump_stack_traces() {
|
||||||
// write a HPROF_TRACE record without any frames to be referenced as object alloc sites
|
// write a HPROF_TRACE record without any frames to be referenced as object alloc sites
|
||||||
DumperSupport::write_header(writer(), HPROF_TRACE, 3*sizeof(u4));
|
DumperSupport::write_header(writer(), HPROF_TRACE, 3 * sizeof(u4));
|
||||||
writer()->write_u4((u4) STACK_TRACE_ID);
|
writer()->write_u4((u4)STACK_TRACE_ID);
|
||||||
writer()->write_u4(0); // thread number
|
writer()->write_u4(0); // thread number
|
||||||
writer()->write_u4(0); // frame count
|
writer()->write_u4(0); // frame count
|
||||||
|
|
||||||
_stack_traces = NEW_C_HEAP_ARRAY(ThreadStackTrace*, Threads::number_of_threads(), mtInternal);
|
// max number if every platform thread is carrier with mounted virtual thread
|
||||||
int frame_serial_num = 0;
|
_thread_dumpers = NEW_C_HEAP_ARRAY(ThreadDumper*, Threads::number_of_threads() * 2, mtInternal);
|
||||||
for (JavaThreadIteratorWithHandle jtiwh; JavaThread *thread = jtiwh.next(); ) {
|
|
||||||
oop threadObj = thread->threadObj();
|
|
||||||
if (threadObj != nullptr && !thread->is_exiting() && !thread->is_hidden_from_external_view()) {
|
|
||||||
// dump thread stack trace
|
|
||||||
Thread* current_thread = Thread::current();
|
|
||||||
ResourceMark rm(current_thread);
|
|
||||||
HandleMark hm(current_thread);
|
|
||||||
|
|
||||||
ThreadStackTrace* stack_trace = new ThreadStackTrace(thread, false);
|
for (JavaThreadIteratorWithHandle jtiwh; JavaThread * thread = jtiwh.next(); ) {
|
||||||
stack_trace->dump_stack_at_safepoint(-1, /* ObjectMonitorsHashtable is not needed here */ nullptr, true);
|
if (ThreadDumper::should_dump_pthread(thread)) {
|
||||||
_stack_traces[_num_threads++] = stack_trace;
|
bool add_oom_frame = is_oom_thread(thread);
|
||||||
|
|
||||||
// write HPROF_FRAME records for this thread's stack trace
|
oop mounted_vt = thread->is_vthread_mounted() ? thread->vthread() : nullptr;
|
||||||
int depth = stack_trace->get_stack_depth();
|
if (mounted_vt != nullptr && !ThreadDumper::should_dump_vthread(mounted_vt)) {
|
||||||
int thread_frame_start = frame_serial_num;
|
mounted_vt = nullptr;
|
||||||
int extra_frames = 0;
|
|
||||||
// write fake frame that makes it look like the thread, which caused OOME,
|
|
||||||
// is in the OutOfMemoryError zero-parameter constructor
|
|
||||||
if (thread == _oome_thread && _oome_constructor != nullptr) {
|
|
||||||
int oome_serial_num = _klass_map->find(_oome_constructor->method_holder());
|
|
||||||
// the class serial number starts from 1
|
|
||||||
assert(oome_serial_num > 0, "OutOfMemoryError class not found");
|
|
||||||
DumperSupport::dump_stack_frame(writer(), ++frame_serial_num, oome_serial_num,
|
|
||||||
_oome_constructor, 0);
|
|
||||||
extra_frames++;
|
|
||||||
}
|
}
|
||||||
for (int j=0; j < depth; j++) {
|
|
||||||
StackFrameInfo* frame = stack_trace->stack_frame_at(j);
|
|
||||||
Method* m = frame->method();
|
|
||||||
int class_serial_num = _klass_map->find(m->method_holder());
|
|
||||||
// the class serial number starts from 1
|
|
||||||
assert(class_serial_num > 0, "class not found");
|
|
||||||
DumperSupport::dump_stack_frame(writer(), ++frame_serial_num, class_serial_num, m, frame->bci());
|
|
||||||
}
|
|
||||||
depth += extra_frames;
|
|
||||||
|
|
||||||
// write HPROF_TRACE record for one thread
|
// mounted vthread (if any)
|
||||||
DumperSupport::write_header(writer(), HPROF_TRACE, checked_cast<u4>(3*sizeof(u4) + depth*oopSize));
|
if (mounted_vt != nullptr) {
|
||||||
int stack_serial_num = _num_threads + STACK_TRACE_ID;
|
ThreadDumper* thread_dumper = new ThreadDumper(ThreadDumper::ThreadType::MountedVirtual, thread, mounted_vt);
|
||||||
writer()->write_u4(stack_serial_num); // stack trace serial number
|
_thread_dumpers[_thread_dumpers_count++] = thread_dumper;
|
||||||
writer()->write_u4((u4) _num_threads); // thread serial number
|
if (add_oom_frame) {
|
||||||
writer()->write_u4(depth); // frame count
|
thread_dumper->add_oom_frame(_oome_constructor);
|
||||||
for (int j=1; j <= depth; j++) {
|
// we add oom frame to the VT stack, don't add it to the carrier thread stack
|
||||||
writer()->write_id(thread_frame_start + j);
|
add_oom_frame = false;
|
||||||
}
|
}
|
||||||
|
thread_dumper->init_serial_nums(&_thread_serial_num, &_frame_serial_num);
|
||||||
|
thread_dumper->dump_stack_traces(writer(), _klass_map);
|
||||||
|
}
|
||||||
|
|
||||||
|
// platform or carrier thread
|
||||||
|
ThreadDumper* thread_dumper = new ThreadDumper(ThreadDumper::ThreadType::Platform, thread, thread->threadObj());
|
||||||
|
_thread_dumpers[_thread_dumpers_count++] = thread_dumper;
|
||||||
|
if (add_oom_frame) {
|
||||||
|
thread_dumper->add_oom_frame(_oome_constructor);
|
||||||
|
}
|
||||||
|
thread_dumper->init_serial_nums(&_thread_serial_num, &_frame_serial_num);
|
||||||
|
thread_dumper->dump_stack_traces(writer(), _klass_map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,300 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.lang.ref.Reference;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import jdk.test.lib.Asserts;
|
||||||
|
import jdk.test.lib.JDKToolLauncher;
|
||||||
|
import jdk.test.lib.apps.LingeredApp;
|
||||||
|
import jdk.test.lib.process.ProcessTools;
|
||||||
|
|
||||||
|
import jdk.test.lib.hprof.model.JavaClass;
|
||||||
|
import jdk.test.lib.hprof.model.JavaHeapObject;
|
||||||
|
import jdk.test.lib.hprof.model.Root;
|
||||||
|
import jdk.test.lib.hprof.model.Snapshot;
|
||||||
|
import jdk.test.lib.hprof.model.StackFrame;
|
||||||
|
import jdk.test.lib.hprof.model.StackTrace;
|
||||||
|
import jdk.test.lib.hprof.model.ThreadObject;
|
||||||
|
import jdk.test.lib.hprof.parser.Reader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test id=default
|
||||||
|
* @requires vm.jvmti
|
||||||
|
* @requires vm.continuations
|
||||||
|
* @library /test/lib
|
||||||
|
* @run main VThreadInHeapDump
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test id=no-vmcontinuations
|
||||||
|
* @requires vm.jvmti
|
||||||
|
* @library /test/lib
|
||||||
|
* @comment pass extra VM arguments as the test arguments
|
||||||
|
* @run main VThreadInHeapDump
|
||||||
|
* -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations
|
||||||
|
*/
|
||||||
|
|
||||||
|
class VThreadInHeapDumpTarg extends LingeredApp {
|
||||||
|
|
||||||
|
public static class VThreadUnmountedReferenced {
|
||||||
|
}
|
||||||
|
public static class VThreadMountedReferenced {
|
||||||
|
}
|
||||||
|
public static class PThreadReferenced {
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ThreadBase {
|
||||||
|
private volatile boolean threadReady = false;
|
||||||
|
|
||||||
|
protected void ready() {
|
||||||
|
threadReady = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void waitReady() {
|
||||||
|
while (!threadReady) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(10);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VthreadUnmounted extends ThreadBase implements Runnable {
|
||||||
|
public void run() {
|
||||||
|
Object referenced = new VThreadUnmountedReferenced();
|
||||||
|
ready();
|
||||||
|
// The thread will be unmounted in awaitToStop().
|
||||||
|
awaitToStop();
|
||||||
|
Reference.reachabilityFence(referenced);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VthreadMounted extends ThreadBase implements Runnable {
|
||||||
|
int dummy = -1;
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
Object referenced = new VThreadMountedReferenced();
|
||||||
|
ready();
|
||||||
|
// Don't give a chance for the thread to unmount.
|
||||||
|
while (!timeToStop) {
|
||||||
|
if (++dummy == 10000) {
|
||||||
|
dummy = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference.reachabilityFence(referenced);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Pthread extends ThreadBase implements Runnable {
|
||||||
|
public void run() {
|
||||||
|
Object referenced = new PThreadReferenced();
|
||||||
|
ready();
|
||||||
|
awaitToStop();
|
||||||
|
Reference.reachabilityFence(referenced);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CountDownLatch timeToStopLatch = new CountDownLatch(1);
|
||||||
|
volatile boolean timeToStop = false;
|
||||||
|
|
||||||
|
void awaitToStop() {
|
||||||
|
try {
|
||||||
|
timeToStopLatch.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runTest(String[] args) {
|
||||||
|
try {
|
||||||
|
// Unmounted virtual thread.
|
||||||
|
VthreadUnmounted vthreadUnmounted = new VthreadUnmounted();
|
||||||
|
Thread.ofVirtual().start(vthreadUnmounted);
|
||||||
|
vthreadUnmounted.waitReady();
|
||||||
|
|
||||||
|
// Mounted virtual thread.
|
||||||
|
VthreadMounted vthreadMounted = new VthreadMounted();
|
||||||
|
Thread.ofVirtual().start(vthreadMounted);
|
||||||
|
vthreadMounted.waitReady();
|
||||||
|
|
||||||
|
// Platform thread.
|
||||||
|
Pthread pthread = new Pthread();
|
||||||
|
Thread.ofPlatform().start(pthread);
|
||||||
|
pthread.waitReady();
|
||||||
|
|
||||||
|
// We are ready.
|
||||||
|
LingeredApp.main(args);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
// Signal all threads to finish.
|
||||||
|
timeToStop = true;
|
||||||
|
timeToStopLatch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
VThreadInHeapDumpTarg test = new VThreadInHeapDumpTarg();
|
||||||
|
test.runTest(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class VThreadInHeapDump {
|
||||||
|
|
||||||
|
// test arguments are extra VM options for target process
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
File dumpFile = new File("Myheapdump.hprof");
|
||||||
|
createDump(dumpFile, args);
|
||||||
|
verifyDump(dumpFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createDump(File dumpFile, String[] extraOptions) throws Exception {
|
||||||
|
LingeredApp theApp = null;
|
||||||
|
try {
|
||||||
|
theApp = new VThreadInHeapDumpTarg();
|
||||||
|
|
||||||
|
List<String> extraVMArgs = new ArrayList<>();
|
||||||
|
extraVMArgs.add("-Djdk.virtualThreadScheduler.parallelism=1");
|
||||||
|
extraVMArgs.addAll(Arrays.asList(extraOptions));
|
||||||
|
LingeredApp.startApp(theApp, extraVMArgs.toArray(new String[0]));
|
||||||
|
|
||||||
|
//jcmd <pid> GC.heap_dump <file_path>
|
||||||
|
JDKToolLauncher launcher = JDKToolLauncher
|
||||||
|
.createUsingTestJDK("jcmd")
|
||||||
|
.addToolArg(Long.toString(theApp.getPid()))
|
||||||
|
.addToolArg("GC.heap_dump")
|
||||||
|
.addToolArg(dumpFile.getAbsolutePath());
|
||||||
|
Process p = ProcessTools.startProcess("jcmd", new ProcessBuilder(launcher.getCommand()));
|
||||||
|
// If something goes wrong with heap dumping most likely we'll get crash of the target VM.
|
||||||
|
while (!p.waitFor(5, TimeUnit.SECONDS)) {
|
||||||
|
if (!theApp.getProcess().isAlive()) {
|
||||||
|
log("ERROR: target VM died, killing jcmd...");
|
||||||
|
p.destroyForcibly();
|
||||||
|
throw new Exception("Target VM died");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p.exitValue() != 0) {
|
||||||
|
throw new Exception("Jcmd exited with code " + p.exitValue());
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
LingeredApp.stopApp(theApp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void verifyDump(File dumpFile) throws Exception {
|
||||||
|
Asserts.assertTrue(dumpFile.exists(), "Heap dump file not found.");
|
||||||
|
|
||||||
|
log("Reading " + dumpFile + "...");
|
||||||
|
try (Snapshot snapshot = Reader.readFile(dumpFile.getPath(), true, 0)) {
|
||||||
|
log("Resolving snapshot...");
|
||||||
|
snapshot.resolve(true);
|
||||||
|
log("Snapshot resolved.");
|
||||||
|
|
||||||
|
// Log all threads with stack traces and stack references.
|
||||||
|
List<ThreadObject> threads = snapshot.getThreads();
|
||||||
|
List<Root> roots = Collections.list(snapshot.getRoots());
|
||||||
|
log("Threads:");
|
||||||
|
for (ThreadObject thread: threads) {
|
||||||
|
StackTrace st = thread.getStackTrace();
|
||||||
|
StackFrame[] frames = st.getFrames();
|
||||||
|
log("thread " + thread.getIdString() + ", " + frames.length + " frames");
|
||||||
|
|
||||||
|
List<Root> stackRoots = findStackRoot(roots, thread);
|
||||||
|
for (int i = 0; i < frames.length; i++) {
|
||||||
|
log(" - [" + i + "] "
|
||||||
|
+ frames[i].getClassName() + "." + frames[i].getMethodName()
|
||||||
|
+ frames[i].getMethodSignature()
|
||||||
|
+ " (" + frames[i].getSourceFileName()
|
||||||
|
+ ":" + frames[i].getLineNumber() + ")");
|
||||||
|
|
||||||
|
for (Root r: stackRoots) {
|
||||||
|
StackFrame[] rootFrames = r.getStackTrace().getFrames();
|
||||||
|
// the frame this local belongs to
|
||||||
|
StackFrame frame = rootFrames[rootFrames.length - 1];
|
||||||
|
if (frame == frames[i]) {
|
||||||
|
JavaHeapObject obj = snapshot.findThing(r.getId());
|
||||||
|
JavaClass objClass = obj.getClazz();
|
||||||
|
log(" " + r.getDescription() + ": " + objClass.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify objects from thread stacks are dumped.
|
||||||
|
test(snapshot, VThreadInHeapDumpTarg.VThreadMountedReferenced.class);
|
||||||
|
test(snapshot, VThreadInHeapDumpTarg.PThreadReferenced.class);
|
||||||
|
// Dumping of unmounted vthreads is not implemented yet
|
||||||
|
//test(snapshot, VThreadInHeapDumpTarg.VThreadUnmountedReferenced.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Root> findStackRoot(List<Root> roots, ThreadObject thread) {
|
||||||
|
List<Root> result = new ArrayList<>();
|
||||||
|
for (Root root: roots) {
|
||||||
|
if (root.getRefererId() == thread.getId()) {
|
||||||
|
result.add(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void test(Snapshot snapshot, String className) {
|
||||||
|
log("Testing " + className + "...");
|
||||||
|
JavaClass jClass = snapshot.findClass(className);
|
||||||
|
if (jClass == null) {
|
||||||
|
throw new RuntimeException("'" + className + "' not found");
|
||||||
|
}
|
||||||
|
int instanceCount = jClass.getInstancesCount(false);
|
||||||
|
if (instanceCount != 1) {
|
||||||
|
throw new RuntimeException("Expected 1 instance, " + instanceCount + " instances found");
|
||||||
|
}
|
||||||
|
// There is the only instance.
|
||||||
|
JavaHeapObject heapObj = jClass.getInstances(false).nextElement();
|
||||||
|
|
||||||
|
Root root = heapObj.getRoot();
|
||||||
|
if (root == null) {
|
||||||
|
throw new RuntimeException("No root for " + className + " instance");
|
||||||
|
}
|
||||||
|
log(" root: " + root.getDescription());
|
||||||
|
JavaHeapObject referrer = root.getReferer();
|
||||||
|
log(" referrer: " + referrer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void test(Snapshot snapshot, Class cls) {
|
||||||
|
test(snapshot, cls.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void log(Object s) {
|
||||||
|
System.out.println(s);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -141,6 +141,10 @@ public class Root {
|
|||||||
return referer;
|
return referer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getRefererId() {
|
||||||
|
return refererId;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the stack trace responsible for this root, or null if there
|
* @return the stack trace responsible for this root, or null if there
|
||||||
* is none.
|
* is none.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -82,6 +82,9 @@ public class Snapshot implements AutoCloseable {
|
|||||||
// soft cache of finalizeable objects - lazily initialized
|
// soft cache of finalizeable objects - lazily initialized
|
||||||
private SoftReference<Vector<?>> finalizablesCache;
|
private SoftReference<Vector<?>> finalizablesCache;
|
||||||
|
|
||||||
|
// threads
|
||||||
|
private ArrayList<ThreadObject> threads = new ArrayList<>();
|
||||||
|
|
||||||
// represents null reference
|
// represents null reference
|
||||||
private JavaThing nullThing;
|
private JavaThing nullThing;
|
||||||
|
|
||||||
@ -175,6 +178,10 @@ public class Snapshot implements AutoCloseable {
|
|||||||
putInClassesMap(c);
|
putInClassesMap(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addThreadObject(ThreadObject thread) {
|
||||||
|
threads.add(thread);
|
||||||
|
}
|
||||||
|
|
||||||
JavaClass addFakeInstanceClass(long classID, int instSize) {
|
JavaClass addFakeInstanceClass(long classID, int instSize) {
|
||||||
// Create a fake class name based on ID.
|
// Create a fake class name based on ID.
|
||||||
String name = "unknown-class<@" + Misc.toHex(classID) + ">";
|
String name = "unknown-class<@" + Misc.toHex(classID) + ">";
|
||||||
@ -433,6 +440,10 @@ public class Snapshot implements AutoCloseable {
|
|||||||
return roots.elementAt(i);
|
return roots.elementAt(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ThreadObject> getThreads() {
|
||||||
|
return Collections.unmodifiableList(threads);
|
||||||
|
}
|
||||||
|
|
||||||
public ReferenceChain[]
|
public ReferenceChain[]
|
||||||
rootsetReferencesTo(JavaHeapObject target, boolean includeWeak) {
|
rootsetReferencesTo(JavaHeapObject target, boolean includeWeak) {
|
||||||
Vector<ReferenceChain> fifo = new Vector<ReferenceChain>(); // This is slow... A real fifo would help
|
Vector<ReferenceChain> fifo = new Vector<ReferenceChain>(); // This is slow... A real fifo would help
|
||||||
|
56
test/lib/jdk/test/lib/hprof/model/ThreadObject.java
Normal file
56
test/lib/jdk/test/lib/hprof/model/ThreadObject.java
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jdk.test.lib.hprof.model;
|
||||||
|
|
||||||
|
import jdk.test.lib.hprof.util.Misc;
|
||||||
|
|
||||||
|
public class ThreadObject {
|
||||||
|
|
||||||
|
private final long id; // ID of the JavaThing we refer to
|
||||||
|
private final StackTrace stackTrace;
|
||||||
|
|
||||||
|
public ThreadObject(long id, StackTrace stackTrace) {
|
||||||
|
this.id = id;
|
||||||
|
this.stackTrace = stackTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIdString() {
|
||||||
|
return Misc.toHex(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StackTrace getStackTrace() {
|
||||||
|
return stackTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
void resolve(Snapshot ss) {
|
||||||
|
if (stackTrace != null) {
|
||||||
|
stackTrace.resolve(ss);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 1997, 2020, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -436,8 +436,10 @@ public class HprofReader extends Reader /* imports */ implements ArrayTypeCodes
|
|||||||
int threadSeq = in.readInt();
|
int threadSeq = in.readInt();
|
||||||
int stackSeq = in.readInt();
|
int stackSeq = in.readInt();
|
||||||
bytesLeft -= identifierSize + 8;
|
bytesLeft -= identifierSize + 8;
|
||||||
threadObjects.put(threadSeq,
|
StackTrace st = getStackTraceFromSerial(stackSeq);
|
||||||
new ThreadObject(id, stackSeq));
|
ThreadObject threadObj = new ThreadObject(id, st);
|
||||||
|
threadObjects.put(threadSeq, threadObj);
|
||||||
|
snapshot.addThreadObject(threadObj);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case HPROF_GC_ROOT_JNI_GLOBAL: {
|
case HPROF_GC_ROOT_JNI_GLOBAL: {
|
||||||
@ -453,11 +455,11 @@ public class HprofReader extends Reader /* imports */ implements ArrayTypeCodes
|
|||||||
int depth = in.readInt();
|
int depth = in.readInt();
|
||||||
bytesLeft -= identifierSize + 8;
|
bytesLeft -= identifierSize + 8;
|
||||||
ThreadObject to = getThreadObjectFromSequence(threadSeq);
|
ThreadObject to = getThreadObjectFromSequence(threadSeq);
|
||||||
StackTrace st = getStackTraceFromSerial(to.stackSeq);
|
StackTrace st = to.getStackTrace();
|
||||||
if (st != null) {
|
if (st != null) {
|
||||||
st = st.traceForDepth(depth+1);
|
st = st.traceForDepth(depth+1);
|
||||||
}
|
}
|
||||||
snapshot.addRoot(new Root(id, to.threadId,
|
snapshot.addRoot(new Root(id, to.getId(),
|
||||||
Root.NATIVE_LOCAL, "", st));
|
Root.NATIVE_LOCAL, "", st));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -467,11 +469,11 @@ public class HprofReader extends Reader /* imports */ implements ArrayTypeCodes
|
|||||||
int depth = in.readInt();
|
int depth = in.readInt();
|
||||||
bytesLeft -= identifierSize + 8;
|
bytesLeft -= identifierSize + 8;
|
||||||
ThreadObject to = getThreadObjectFromSequence(threadSeq);
|
ThreadObject to = getThreadObjectFromSequence(threadSeq);
|
||||||
StackTrace st = getStackTraceFromSerial(to.stackSeq);
|
StackTrace st = to.getStackTrace();;
|
||||||
if (st != null) {
|
if (st != null) {
|
||||||
st = st.traceForDepth(depth+1);
|
st = st.traceForDepth(depth+1);
|
||||||
}
|
}
|
||||||
snapshot.addRoot(new Root(id, to.threadId,
|
snapshot.addRoot(new Root(id, to.getId(),
|
||||||
Root.JAVA_LOCAL, "", st));
|
Root.JAVA_LOCAL, "", st));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -480,8 +482,8 @@ public class HprofReader extends Reader /* imports */ implements ArrayTypeCodes
|
|||||||
int threadSeq = in.readInt();
|
int threadSeq = in.readInt();
|
||||||
bytesLeft -= identifierSize + 4;
|
bytesLeft -= identifierSize + 4;
|
||||||
ThreadObject to = getThreadObjectFromSequence(threadSeq);
|
ThreadObject to = getThreadObjectFromSequence(threadSeq);
|
||||||
StackTrace st = getStackTraceFromSerial(to.stackSeq);
|
StackTrace st = to.getStackTrace();;
|
||||||
snapshot.addRoot(new Root(id, to.threadId,
|
snapshot.addRoot(new Root(id, to.getId(),
|
||||||
Root.NATIVE_STACK, "", st));
|
Root.NATIVE_STACK, "", st));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -496,8 +498,8 @@ public class HprofReader extends Reader /* imports */ implements ArrayTypeCodes
|
|||||||
int threadSeq = in.readInt();
|
int threadSeq = in.readInt();
|
||||||
bytesLeft -= identifierSize + 4;
|
bytesLeft -= identifierSize + 4;
|
||||||
ThreadObject to = getThreadObjectFromSequence(threadSeq);
|
ThreadObject to = getThreadObjectFromSequence(threadSeq);
|
||||||
StackTrace st = getStackTraceFromSerial(to.stackSeq);
|
StackTrace st = to.getStackTrace();
|
||||||
snapshot.addRoot(new Root(id, to.threadId,
|
snapshot.addRoot(new Root(id, to.getId(),
|
||||||
Root.THREAD_BLOCK, "", st));
|
Root.THREAD_BLOCK, "", st));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -913,18 +915,4 @@ public class HprofReader extends Reader /* imports */ implements ArrayTypeCodes
|
|||||||
System.out.println("WARNING: " + msg);
|
System.out.println("WARNING: " + msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// A trivial data-holder class for HPROF_GC_ROOT_THREAD_OBJ.
|
|
||||||
//
|
|
||||||
private class ThreadObject {
|
|
||||||
|
|
||||||
long threadId;
|
|
||||||
int stackSeq;
|
|
||||||
|
|
||||||
ThreadObject(long threadId, int stackSeq) {
|
|
||||||
this.threadId = threadId;
|
|
||||||
this.stackSeq = stackSeq;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user