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/oop.inline.hpp"
|
||||
#include "oops/typeArrayOop.inline.hpp"
|
||||
#include "runtime/continuationWrapper.inline.hpp"
|
||||
#include "runtime/frame.inline.hpp"
|
||||
#include "runtime/handles.inline.hpp"
|
||||
#include "runtime/javaCalls.hpp"
|
||||
@ -1385,7 +1386,6 @@ class JNILocalsDumper : public OopClosure {
|
||||
void do_oop(narrowOop* obj_p) { ShouldNotReachHere(); }
|
||||
};
|
||||
|
||||
|
||||
void JNILocalsDumper::do_oop(oop* obj_p) {
|
||||
// ignore null handles
|
||||
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;
|
||||
|
||||
// Support class using when iterating over the heap.
|
||||
@ -1683,8 +1987,12 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask {
|
||||
Method* _oome_constructor;
|
||||
bool _gc_before_heap_dump;
|
||||
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;
|
||||
// parallel heap dump support
|
||||
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
|
||||
static void do_class_dump(Klass* k);
|
||||
|
||||
// HPROF_GC_ROOT_THREAD_OBJ records
|
||||
int do_thread(JavaThread* thread, u4 thread_serial_num);
|
||||
void do_threads();
|
||||
// HPROF_GC_ROOT_THREAD_OBJ records for platform and mounted virtual threads
|
||||
void dump_threads();
|
||||
|
||||
void add_class_serial_number(Klass* k, int serial_num) {
|
||||
_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();
|
||||
|
||||
public:
|
||||
@ -1742,8 +2053,12 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask {
|
||||
_local_writer = writer;
|
||||
_gc_before_heap_dump = gc_before_heap_dump;
|
||||
_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;
|
||||
_num_dumper_threads = num_dump_threads;
|
||||
_dumper_controller = nullptr;
|
||||
@ -1763,12 +2078,13 @@ class VM_HeapDumper : public VM_GC_Operation, public WorkerTask {
|
||||
}
|
||||
|
||||
~VM_HeapDumper() {
|
||||
if (_stack_traces != nullptr) {
|
||||
for (int i=0; i < _num_threads; i++) {
|
||||
delete _stack_traces[i];
|
||||
if (_thread_dumpers != nullptr) {
|
||||
for (int i = 0; i < _thread_dumpers_count; i++) {
|
||||
delete _thread_dumpers[i];
|
||||
}
|
||||
FREE_C_HEAP_ARRAY(ThreadStackTrace*, _stack_traces);
|
||||
FREE_C_HEAP_ARRAY(ThreadDumper*, _thread_dumpers);
|
||||
}
|
||||
|
||||
if (_dumper_controller != nullptr) {
|
||||
delete _dumper_controller;
|
||||
_dumper_controller = nullptr;
|
||||
@ -1835,127 +2151,13 @@ void VM_HeapDumper::do_class_dump(Klass* k) {
|
||||
}
|
||||
}
|
||||
|
||||
// Walk the stack of the given thread.
|
||||
// Dumps a HPROF_GC_ROOT_JAVA_FRAME record for each local
|
||||
// Dumps a HPROF_GC_ROOT_JNI_LOCAL record for each JNI local
|
||||
//
|
||||
// It returns the number of Java frames in this thread stack
|
||||
int VM_HeapDumper::do_thread(JavaThread* java_thread, u4 thread_serial_num) {
|
||||
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++;
|
||||
// Write a HPROF_GC_ROOT_THREAD_OBJ record for platform/carrier and mounted virtual threads.
|
||||
// Then walk the stack so that locals and JNI locals are dumped.
|
||||
void VM_HeapDumper::dump_threads() {
|
||||
for (int i = 0; i < _thread_dumpers_count; i++) {
|
||||
_thread_dumpers[i]->dump_thread_obj(writer());
|
||||
_thread_dumpers[i]->dump_stack_refs(writer());
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
bool VM_HeapDumper::doit_prologue() {
|
||||
@ -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.
|
||||
dump_stack_traces();
|
||||
|
||||
// HPROF_HEAP_DUMP/HPROF_HEAP_DUMP_SEGMENT starts here
|
||||
|
||||
// Writes HPROF_GC_CLASS_DUMP records
|
||||
{
|
||||
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
|
||||
do_threads();
|
||||
dump_threads();
|
||||
|
||||
// HPROF_GC_ROOT_JNI_GLOBAL
|
||||
JNIGlobalsDumper jni_dumper(writer());
|
||||
@ -2163,58 +2367,44 @@ void VM_HeapDumper::work(uint worker_id) {
|
||||
|
||||
void VM_HeapDumper::dump_stack_traces() {
|
||||
// write a HPROF_TRACE record without any frames to be referenced as object alloc sites
|
||||
DumperSupport::write_header(writer(), HPROF_TRACE, 3*sizeof(u4));
|
||||
writer()->write_u4((u4) STACK_TRACE_ID);
|
||||
DumperSupport::write_header(writer(), HPROF_TRACE, 3 * sizeof(u4));
|
||||
writer()->write_u4((u4)STACK_TRACE_ID);
|
||||
writer()->write_u4(0); // thread number
|
||||
writer()->write_u4(0); // frame count
|
||||
|
||||
_stack_traces = NEW_C_HEAP_ARRAY(ThreadStackTrace*, Threads::number_of_threads(), mtInternal);
|
||||
int frame_serial_num = 0;
|
||||
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);
|
||||
// max number if every platform thread is carrier with mounted virtual thread
|
||||
_thread_dumpers = NEW_C_HEAP_ARRAY(ThreadDumper*, Threads::number_of_threads() * 2, mtInternal);
|
||||
|
||||
ThreadStackTrace* stack_trace = new ThreadStackTrace(thread, false);
|
||||
stack_trace->dump_stack_at_safepoint(-1, /* ObjectMonitorsHashtable is not needed here */ nullptr, true);
|
||||
_stack_traces[_num_threads++] = stack_trace;
|
||||
for (JavaThreadIteratorWithHandle jtiwh; JavaThread * thread = jtiwh.next(); ) {
|
||||
if (ThreadDumper::should_dump_pthread(thread)) {
|
||||
bool add_oom_frame = is_oom_thread(thread);
|
||||
|
||||
// write HPROF_FRAME records for this thread's stack trace
|
||||
int depth = stack_trace->get_stack_depth();
|
||||
int thread_frame_start = frame_serial_num;
|
||||
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++;
|
||||
oop mounted_vt = thread->is_vthread_mounted() ? thread->vthread() : nullptr;
|
||||
if (mounted_vt != nullptr && !ThreadDumper::should_dump_vthread(mounted_vt)) {
|
||||
mounted_vt = nullptr;
|
||||
}
|
||||
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
|
||||
DumperSupport::write_header(writer(), HPROF_TRACE, checked_cast<u4>(3*sizeof(u4) + depth*oopSize));
|
||||
int stack_serial_num = _num_threads + STACK_TRACE_ID;
|
||||
writer()->write_u4(stack_serial_num); // stack trace serial number
|
||||
writer()->write_u4((u4) _num_threads); // thread serial number
|
||||
writer()->write_u4(depth); // frame count
|
||||
for (int j=1; j <= depth; j++) {
|
||||
writer()->write_id(thread_frame_start + j);
|
||||
// mounted vthread (if any)
|
||||
if (mounted_vt != nullptr) {
|
||||
ThreadDumper* thread_dumper = new ThreadDumper(ThreadDumper::ThreadType::MountedVirtual, thread, mounted_vt);
|
||||
_thread_dumpers[_thread_dumpers_count++] = thread_dumper;
|
||||
if (add_oom_frame) {
|
||||
thread_dumper->add_oom_frame(_oome_constructor);
|
||||
// we add oom frame to the VT stack, don't add it to the carrier thread stack
|
||||
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.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -141,6 +141,10 @@ public class Root {
|
||||
return referer;
|
||||
}
|
||||
|
||||
public long getRefererId() {
|
||||
return refererId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the stack trace responsible for this root, or null if there
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
private SoftReference<Vector<?>> finalizablesCache;
|
||||
|
||||
// threads
|
||||
private ArrayList<ThreadObject> threads = new ArrayList<>();
|
||||
|
||||
// represents null reference
|
||||
private JavaThing nullThing;
|
||||
|
||||
@ -175,6 +178,10 @@ public class Snapshot implements AutoCloseable {
|
||||
putInClassesMap(c);
|
||||
}
|
||||
|
||||
public void addThreadObject(ThreadObject thread) {
|
||||
threads.add(thread);
|
||||
}
|
||||
|
||||
JavaClass addFakeInstanceClass(long classID, int instSize) {
|
||||
// Create a fake class name based on ID.
|
||||
String name = "unknown-class<@" + Misc.toHex(classID) + ">";
|
||||
@ -433,6 +440,10 @@ public class Snapshot implements AutoCloseable {
|
||||
return roots.elementAt(i);
|
||||
}
|
||||
|
||||
public List<ThreadObject> getThreads() {
|
||||
return Collections.unmodifiableList(threads);
|
||||
}
|
||||
|
||||
public ReferenceChain[]
|
||||
rootsetReferencesTo(JavaHeapObject target, boolean includeWeak) {
|
||||
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.
|
||||
*
|
||||
* 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 stackSeq = in.readInt();
|
||||
bytesLeft -= identifierSize + 8;
|
||||
threadObjects.put(threadSeq,
|
||||
new ThreadObject(id, stackSeq));
|
||||
StackTrace st = getStackTraceFromSerial(stackSeq);
|
||||
ThreadObject threadObj = new ThreadObject(id, st);
|
||||
threadObjects.put(threadSeq, threadObj);
|
||||
snapshot.addThreadObject(threadObj);
|
||||
break;
|
||||
}
|
||||
case HPROF_GC_ROOT_JNI_GLOBAL: {
|
||||
@ -453,11 +455,11 @@ public class HprofReader extends Reader /* imports */ implements ArrayTypeCodes
|
||||
int depth = in.readInt();
|
||||
bytesLeft -= identifierSize + 8;
|
||||
ThreadObject to = getThreadObjectFromSequence(threadSeq);
|
||||
StackTrace st = getStackTraceFromSerial(to.stackSeq);
|
||||
StackTrace st = to.getStackTrace();
|
||||
if (st != null) {
|
||||
st = st.traceForDepth(depth+1);
|
||||
}
|
||||
snapshot.addRoot(new Root(id, to.threadId,
|
||||
snapshot.addRoot(new Root(id, to.getId(),
|
||||
Root.NATIVE_LOCAL, "", st));
|
||||
break;
|
||||
}
|
||||
@ -467,11 +469,11 @@ public class HprofReader extends Reader /* imports */ implements ArrayTypeCodes
|
||||
int depth = in.readInt();
|
||||
bytesLeft -= identifierSize + 8;
|
||||
ThreadObject to = getThreadObjectFromSequence(threadSeq);
|
||||
StackTrace st = getStackTraceFromSerial(to.stackSeq);
|
||||
StackTrace st = to.getStackTrace();;
|
||||
if (st != null) {
|
||||
st = st.traceForDepth(depth+1);
|
||||
}
|
||||
snapshot.addRoot(new Root(id, to.threadId,
|
||||
snapshot.addRoot(new Root(id, to.getId(),
|
||||
Root.JAVA_LOCAL, "", st));
|
||||
break;
|
||||
}
|
||||
@ -480,8 +482,8 @@ public class HprofReader extends Reader /* imports */ implements ArrayTypeCodes
|
||||
int threadSeq = in.readInt();
|
||||
bytesLeft -= identifierSize + 4;
|
||||
ThreadObject to = getThreadObjectFromSequence(threadSeq);
|
||||
StackTrace st = getStackTraceFromSerial(to.stackSeq);
|
||||
snapshot.addRoot(new Root(id, to.threadId,
|
||||
StackTrace st = to.getStackTrace();;
|
||||
snapshot.addRoot(new Root(id, to.getId(),
|
||||
Root.NATIVE_STACK, "", st));
|
||||
break;
|
||||
}
|
||||
@ -496,8 +498,8 @@ public class HprofReader extends Reader /* imports */ implements ArrayTypeCodes
|
||||
int threadSeq = in.readInt();
|
||||
bytesLeft -= identifierSize + 4;
|
||||
ThreadObject to = getThreadObjectFromSequence(threadSeq);
|
||||
StackTrace st = getStackTraceFromSerial(to.stackSeq);
|
||||
snapshot.addRoot(new Root(id, to.threadId,
|
||||
StackTrace st = to.getStackTrace();
|
||||
snapshot.addRoot(new Root(id, to.getId(),
|
||||
Root.THREAD_BLOCK, "", st));
|
||||
break;
|
||||
}
|
||||
@ -913,18 +915,4 @@ public class HprofReader extends Reader /* imports */ implements ArrayTypeCodes
|
||||
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