8316691: Heap dump: separate stack traces for mounted virtual threads

Reviewed-by: lmesnik, sspitsyn
This commit is contained in:
Alex Menkov 2023-10-10 20:13:09 +00:00
parent 33591a30d2
commit 2b8276aa52
6 changed files with 754 additions and 205 deletions

View File

@ -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(&reg_map);
vframe* vf = vframe::new_vframe(&fr, &reg_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(&reg_map)
: _java_thread->platform_thread_last_java_vframe(&reg_map);
case ThreadType::MountedVirtual:
return _java_thread->last_java_vframe(&reg_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, &reg_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);
}
}
}

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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

View 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);
}
}
}

View File

@ -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;
}
}
}