8357650: ThreadSnapshot to take snapshot of thread for thread dumps

Co-authored-by: Alan Bateman <alanb@openjdk.org>
Co-authored-by: Alex Menkov <amenkov@openjdk.org>
Reviewed-by: sspitsyn, kevinw
This commit is contained in:
Alex Menkov 2025-06-03 18:46:55 +00:00
parent e984fa7997
commit 406f1bc5b9
10 changed files with 716 additions and 4 deletions

View File

@ -1872,7 +1872,7 @@ ByteSize java_lang_Thread::thread_id_offset() {
} }
oop java_lang_Thread::park_blocker(oop java_thread) { oop java_lang_Thread::park_blocker(oop java_thread) {
return java_thread->obj_field(_park_blocker_offset); return java_thread->obj_field_access<MO_RELAXED>(_park_blocker_offset);
} }
oop java_lang_Thread::async_get_stack_trace(oop java_thread, TRAPS) { oop java_lang_Thread::async_get_stack_trace(oop java_thread, TRAPS) {

View File

@ -742,6 +742,12 @@ class SerializeClosure;
template(jdk_internal_vm_ThreadDumper, "jdk/internal/vm/ThreadDumper") \ template(jdk_internal_vm_ThreadDumper, "jdk/internal/vm/ThreadDumper") \
template(dumpThreads_name, "dumpThreads") \ template(dumpThreads_name, "dumpThreads") \
template(dumpThreadsToJson_name, "dumpThreadsToJson") \ template(dumpThreadsToJson_name, "dumpThreadsToJson") \
template(jdk_internal_vm_ThreadSnapshot, "jdk/internal/vm/ThreadSnapshot") \
template(jdk_internal_vm_ThreadLock, "jdk/internal/vm/ThreadSnapshot$ThreadLock") \
template(jdk_internal_vm_ThreadLock_signature, "Ljdk/internal/vm/ThreadSnapshot$ThreadLock;") \
template(jdk_internal_vm_ThreadLock_array, "[Ljdk/internal/vm/ThreadSnapshot$ThreadLock;") \
template(java_lang_StackTraceElement_of_name, "of") \
template(java_lang_StackTraceElement_of_signature, "([Ljava/lang/StackTraceElement;)[Ljava/lang/StackTraceElement;") \
\ \
/* jcmd Thread.vthread_scheduler and Thread.vthread_pollers */ \ /* jcmd Thread.vthread_scheduler and Thread.vthread_pollers */ \
template(jdk_internal_vm_JcmdVThreadCommands, "jdk/internal/vm/JcmdVThreadCommands") \ template(jdk_internal_vm_JcmdVThreadCommands, "jdk/internal/vm/JcmdVThreadCommands") \

View File

@ -301,6 +301,9 @@ JVM_HoldsLock(JNIEnv *env, jclass threadClass, jobject obj);
JNIEXPORT jobject JNICALL JNIEXPORT jobject JNICALL
JVM_GetStackTrace(JNIEnv *env, jobject thread); JVM_GetStackTrace(JNIEnv *env, jobject thread);
JNIEXPORT jobject JNICALL
JVM_CreateThreadSnapshot(JNIEnv* env, jobject thread);
JNIEXPORT jobjectArray JNICALL JNIEXPORT jobjectArray JNICALL
JVM_GetAllThreads(JNIEnv *env, jclass dummy); JVM_GetAllThreads(JNIEnv *env, jclass dummy);

View File

@ -2961,6 +2961,15 @@ JVM_ENTRY(jobject, JVM_GetStackTrace(JNIEnv *env, jobject jthread))
return JNIHandles::make_local(THREAD, trace); return JNIHandles::make_local(THREAD, trace);
JVM_END JVM_END
JVM_ENTRY(jobject, JVM_CreateThreadSnapshot(JNIEnv* env, jobject jthread))
#if INCLUDE_JVMTI
oop snapshot = ThreadSnapshotFactory::get_thread_snapshot(jthread, THREAD);
return JNIHandles::make_local(THREAD, snapshot);
#else
return nullptr;
#endif
JVM_END
JVM_ENTRY(void, JVM_SetNativeThreadName(JNIEnv* env, jobject jthread, jstring name)) JVM_ENTRY(void, JVM_SetNativeThreadName(JNIEnv* env, jobject jthread, jstring name))
// We don't use a ThreadsListHandle here because the current thread // We don't use a ThreadsListHandle here because the current thread
// must be alive. // must be alive.

View File

@ -77,7 +77,7 @@ class JvmtiEnvThreadStateIterator : public StackObj {
// //
// Virtual Thread Mount State Transition (VTMS transition) mechanism // Virtual Thread Mount State Transition (VTMS transition) mechanism
// //
class JvmtiVTMSTransitionDisabler { class JvmtiVTMSTransitionDisabler : public AnyObj {
private: private:
static volatile int _VTMS_transition_disable_for_one_count; // transitions for one virtual thread are disabled while it is positive static volatile int _VTMS_transition_disable_for_one_count; // transitions for one virtual thread are disabled while it is positive
static volatile int _VTMS_transition_disable_for_all_count; // transitions for all virtual threads are disabled while it is positive static volatile int _VTMS_transition_disable_for_all_count; // transitions for all virtual threads are disabled while it is positive

View File

@ -127,7 +127,9 @@ void DCmd::register_dcmds(){
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JVMTIDataDumpDCmd>(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<JVMTIDataDumpDCmd>(full_export, true, false));
#endif // INCLUDE_JVMTI #endif // INCLUDE_JVMTI
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ThreadDumpDCmd>(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ThreadDumpDCmd>(full_export, true, false));
#if INCLUDE_JVMTI
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ThreadDumpToFileDCmd>(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ThreadDumpToFileDCmd>(full_export, true, false));
#endif // INCLUDE_JVMTI
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<VThreadSchedulerDCmd>(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<VThreadSchedulerDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<VThreadPollersDCmd>(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<VThreadPollersDCmd>(full_export, true, false));
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ClassLoaderStatsDCmd>(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ClassLoaderStatsDCmd>(full_export, true, false));

View File

@ -34,21 +34,25 @@
#include "nmt/memTag.hpp" #include "nmt/memTag.hpp"
#include "oops/instanceKlass.hpp" #include "oops/instanceKlass.hpp"
#include "oops/klass.inline.hpp" #include "oops/klass.inline.hpp"
#include "oops/method.inline.hpp"
#include "oops/objArrayKlass.hpp" #include "oops/objArrayKlass.hpp"
#include "oops/objArrayOop.inline.hpp" #include "oops/objArrayOop.inline.hpp"
#include "oops/oop.inline.hpp" #include "oops/oop.inline.hpp"
#include "oops/oopHandle.inline.hpp" #include "oops/oopHandle.inline.hpp"
#include "prims/jvmtiRawMonitor.hpp" #include "prims/jvmtiRawMonitor.hpp"
#include "prims/jvmtiThreadState.hpp"
#include "runtime/atomic.hpp" #include "runtime/atomic.hpp"
#include "runtime/handles.inline.hpp" #include "runtime/handles.inline.hpp"
#include "runtime/init.hpp" #include "runtime/init.hpp"
#include "runtime/javaCalls.hpp"
#include "runtime/javaThread.inline.hpp" #include "runtime/javaThread.inline.hpp"
#include "runtime/jniHandles.inline.hpp"
#include "runtime/objectMonitor.inline.hpp" #include "runtime/objectMonitor.inline.hpp"
#include "runtime/synchronizer.hpp" #include "runtime/synchronizer.inline.hpp"
#include "runtime/thread.inline.hpp" #include "runtime/thread.inline.hpp"
#include "runtime/threads.hpp" #include "runtime/threads.hpp"
#include "runtime/threadSMR.inline.hpp" #include "runtime/threadSMR.inline.hpp"
#include "runtime/vframe.hpp" #include "runtime/vframe.inline.hpp"
#include "runtime/vmThread.hpp" #include "runtime/vmThread.hpp"
#include "runtime/vmOperations.hpp" #include "runtime/vmOperations.hpp"
#include "services/threadService.hpp" #include "services/threadService.hpp"
@ -1115,3 +1119,431 @@ ThreadsListEnumerator::ThreadsListEnumerator(Thread* cur_thread,
_threads_array->append(h); _threads_array->append(h);
} }
} }
// jdk.internal.vm.ThreadSnapshot support
#if INCLUDE_JVMTI
class GetThreadSnapshotClosure: public HandshakeClosure {
private:
static OopStorage* oop_storage() {
assert(_thread_service_storage != nullptr, "sanity");
return _thread_service_storage;
}
public:
struct OwnedLock {
// should be synced with ordinals of jdk.internal.vm.ThreadSnapshot.OwnedLockType enum
enum Type {
NOTHING = -1,
LOCKED = 0,
ELIMINATED = 1,
};
int _frame_depth;
Type _type;
// synchronization object (when type == LOCKED) or its klass (type == ELIMINATED)
OopHandle _obj;
OwnedLock(int depth, Type type, OopHandle obj): _frame_depth(depth), _type(type), _obj(obj) {}
OwnedLock(): _frame_depth(0), _type(NOTHING), _obj(nullptr) {}
};
struct Blocker {
// should be synced with ordinals of jdk.internal.vm.ThreadSnapshot.BlockerLockType enum
enum Type {
NOTHING = -1,
PARK_BLOCKER = 0,
WAITING_TO_LOCK = 1,
WAITING_ON = 2,
};
Type _type;
// park blocker or an object the thread waiting on/trying to lock
OopHandle _obj;
Blocker(Type type, OopHandle obj): _type(type), _obj(obj) {}
Blocker(): _type(NOTHING), _obj(nullptr) {}
bool is_empty() const {
return _type == NOTHING;
}
};
Handle _thread_h;
JavaThread* _java_thread;
int _frame_count; // length of _methods and _bcis arrays
GrowableArray<Method*>* _methods;
GrowableArray<int>* _bcis;
JavaThreadStatus _thread_status;
OopHandle _thread_name;
GrowableArray<OwnedLock>* _locks;
Blocker _blocker;
GetThreadSnapshotClosure(Handle thread_h, JavaThread* java_thread):
HandshakeClosure("GetThreadSnapshotClosure"),
_thread_h(thread_h), _java_thread(java_thread),
_frame_count(0), _methods(nullptr), _bcis(nullptr),
_thread_status(), _thread_name(nullptr),
_locks(nullptr), _blocker() {
}
virtual ~GetThreadSnapshotClosure() {
delete _methods;
delete _bcis;
_thread_name.release(oop_storage());
if (_locks != nullptr) {
for (int i = 0; i < _locks->length(); i++) {
_locks->at(i)._obj.release(oop_storage());
}
delete _locks;
}
_blocker._obj.release(oop_storage());
}
private:
void detect_locks(javaVFrame* jvf, int depth) {
Thread* current = Thread::current();
if (depth == 0 && _blocker.is_empty()) {
// If this is the first frame and it is java.lang.Object.wait(...)
// then print out the receiver.
if (jvf->method()->name() == vmSymbols::wait_name() &&
jvf->method()->method_holder()->name() == vmSymbols::java_lang_Object()) {
OopHandle lock_object;
StackValueCollection* locs = jvf->locals();
if (!locs->is_empty()) {
StackValue* sv = locs->at(0);
if (sv->type() == T_OBJECT) {
Handle o = locs->at(0)->get_obj();
lock_object = OopHandle(oop_storage(), o());
}
}
_blocker = Blocker(Blocker::WAITING_ON, lock_object);
}
}
GrowableArray<MonitorInfo*>* mons = jvf->monitors();
if (!mons->is_empty()) {
for (int index = (mons->length() - 1); index >= 0; index--) {
MonitorInfo* monitor = mons->at(index);
if (monitor->eliminated() && jvf->is_compiled_frame()) { // Eliminated in compiled code
if (monitor->owner_is_scalar_replaced()) {
Klass* k = java_lang_Class::as_Klass(monitor->owner_klass());
_locks->push(OwnedLock(depth, OwnedLock::ELIMINATED, OopHandle(oop_storage(), k->klass_holder())));
} else {
Handle owner(current, monitor->owner());
if (owner.not_null()) {
Klass* k = owner->klass();
_locks->push(OwnedLock(depth, OwnedLock::ELIMINATED, OopHandle(oop_storage(), k->klass_holder())));
}
}
continue;
}
if (monitor->owner() != nullptr) {
// the monitor is associated with an object, i.e., it is locked
if (depth == 0 && _blocker.is_empty()) {
ObjectMonitor* pending_moninor = java_lang_VirtualThread::is_instance(_thread_h())
? java_lang_VirtualThread::current_pending_monitor(_thread_h())
: jvf->thread()->current_pending_monitor();
markWord mark = monitor->owner()->mark();
// The first stage of async deflation does not affect any field
// used by this comparison so the ObjectMonitor* is usable here.
if (mark.has_monitor()) {
ObjectMonitor* mon = ObjectSynchronizer::read_monitor(current, monitor->owner(), mark);
if (// if the monitor is null we must be in the process of locking
mon == nullptr ||
// we have marked ourself as pending on this monitor
mon == pending_moninor ||
// we are not the owner of this monitor
(_java_thread != nullptr && !mon->is_entered(_java_thread))) {
_blocker = Blocker(Blocker::WAITING_TO_LOCK, OopHandle(oop_storage(), monitor->owner()));
continue; // go to next monitor
}
}
}
_locks->push(OwnedLock(depth, OwnedLock::LOCKED, OopHandle(oop_storage(), monitor->owner())));
}
}
}
}
public:
void do_thread(Thread* th) override {
Thread* current = Thread::current();
bool is_virtual = java_lang_VirtualThread::is_instance(_thread_h());
if (_java_thread != nullptr) {
if (is_virtual) {
// mounted vthread, use carrier thread state
oop carrier_thread = java_lang_VirtualThread::carrier_thread(_thread_h());
_thread_status = java_lang_Thread::get_thread_status(carrier_thread);
} else {
_thread_status = java_lang_Thread::get_thread_status(_thread_h());
}
} else {
// unmounted vthread
int vt_state = java_lang_VirtualThread::state(_thread_h());
_thread_status = java_lang_VirtualThread::map_state_to_thread_status(vt_state);
}
_thread_name = OopHandle(oop_storage(), java_lang_Thread::name(_thread_h()));
if (_java_thread != nullptr && !_java_thread->has_last_Java_frame()) {
// stack trace is empty
return;
}
bool vthread_carrier = !is_virtual && (_java_thread != nullptr) && (_java_thread->vthread_continuation() != nullptr);
oop park_blocker = java_lang_Thread::park_blocker(_thread_h());
if (park_blocker != nullptr) {
_blocker = Blocker(Blocker::PARK_BLOCKER, OopHandle(oop_storage(), park_blocker));
}
ResourceMark rm(current);
HandleMark hm(current);
const int max_depth = MaxJavaStackTraceDepth;
const bool skip_hidden = !ShowHiddenFrames;
// Pick minimum length that will cover most cases
int init_length = 64;
_methods = new (mtInternal) GrowableArray<Method*>(init_length, mtInternal);
_bcis = new (mtInternal) GrowableArray<int>(init_length, mtInternal);
_locks = new (mtInternal) GrowableArray<OwnedLock>(init_length, mtInternal);
int total_count = 0;
vframeStream vfst(_java_thread != nullptr
? vframeStream(_java_thread, false, true, vthread_carrier)
: vframeStream(java_lang_VirtualThread::continuation(_thread_h())));
for (;
!vfst.at_end() && (max_depth == 0 || max_depth != total_count);
vfst.next()) {
detect_locks(vfst.asJavaVFrame(), total_count);
if (skip_hidden && (vfst.method()->is_hidden() ||
vfst.method()->is_continuation_enter_intrinsic())) {
continue;
}
_methods->push(vfst.method());
_bcis->push(vfst.bci());
total_count++;
}
_frame_count = total_count;
}
};
class jdk_internal_vm_ThreadLock: AllStatic {
static bool _inited;
static int _depth_offset;
static int _typeOrdinal_offset;
static int _obj_offset;
static void compute_offsets(InstanceKlass* klass, TRAPS) {
JavaClasses::compute_offset(_depth_offset, klass, "depth", vmSymbols::int_signature(), false);
JavaClasses::compute_offset(_typeOrdinal_offset, klass, "typeOrdinal", vmSymbols::int_signature(), false);
JavaClasses::compute_offset(_obj_offset, klass, "obj", vmSymbols::object_signature(), false);
}
public:
static void init(InstanceKlass* klass, TRAPS) {
if (!_inited) {
compute_offsets(klass, CHECK);
_inited = true;
}
}
static Handle create(InstanceKlass* klass, int depth, int type_ordinal, OopHandle obj, TRAPS) {
init(klass, CHECK_NH);
Handle result = klass->allocate_instance_handle(CHECK_NH);
result->int_field_put(_depth_offset, depth);
result->int_field_put(_typeOrdinal_offset, type_ordinal);
result->obj_field_put(_obj_offset, obj.resolve());
return result;
}
};
bool jdk_internal_vm_ThreadLock::_inited = false;
int jdk_internal_vm_ThreadLock::_depth_offset;
int jdk_internal_vm_ThreadLock::_typeOrdinal_offset;
int jdk_internal_vm_ThreadLock::_obj_offset;
class jdk_internal_vm_ThreadSnapshot: AllStatic {
static bool _inited;
static int _name_offset;
static int _threadStatus_offset;
static int _carrierThread_offset;
static int _stackTrace_offset;
static int _locks_offset;
static int _blockerTypeOrdinal_offset;
static int _blockerObject_offset;
static void compute_offsets(InstanceKlass* klass, TRAPS) {
JavaClasses::compute_offset(_name_offset, klass, "name", vmSymbols::string_signature(), false);
JavaClasses::compute_offset(_threadStatus_offset, klass, "threadStatus", vmSymbols::int_signature(), false);
JavaClasses::compute_offset(_carrierThread_offset, klass, "carrierThread", vmSymbols::thread_signature(), false);
JavaClasses::compute_offset(_stackTrace_offset, klass, "stackTrace", vmSymbols::java_lang_StackTraceElement_array(), false);
JavaClasses::compute_offset(_locks_offset, klass, "locks", vmSymbols::jdk_internal_vm_ThreadLock_array(), false);
JavaClasses::compute_offset(_blockerTypeOrdinal_offset, klass, "blockerTypeOrdinal", vmSymbols::int_signature(), false);
JavaClasses::compute_offset(_blockerObject_offset, klass, "blockerObject", vmSymbols::object_signature(), false);
}
public:
static void init(InstanceKlass* klass, TRAPS) {
if (!_inited) {
compute_offsets(klass, CHECK);
_inited = true;
}
}
static Handle allocate(InstanceKlass* klass, TRAPS) {
init(klass, CHECK_NH);
Handle h_k = klass->allocate_instance_handle(CHECK_NH);
return h_k;
}
static void set_name(oop snapshot, oop name) {
snapshot->obj_field_put(_name_offset, name);
}
static void set_thread_status(oop snapshot, int status) {
snapshot->int_field_put(_threadStatus_offset, status);
}
static void set_carrier_thread(oop snapshot, oop carrier_thread) {
snapshot->obj_field_put(_carrierThread_offset, carrier_thread);
}
static void set_stack_trace(oop snapshot, oop trace) {
snapshot->obj_field_put(_stackTrace_offset, trace);
}
static void set_locks(oop snapshot, oop locks) {
snapshot->obj_field_put(_locks_offset, locks);
}
static void set_blocker(oop snapshot, int type_ordinal, oop lock) {
snapshot->int_field_put(_blockerTypeOrdinal_offset, type_ordinal);
snapshot->obj_field_put(_blockerObject_offset, lock);
}
};
bool jdk_internal_vm_ThreadSnapshot::_inited = false;
int jdk_internal_vm_ThreadSnapshot::_name_offset;
int jdk_internal_vm_ThreadSnapshot::_threadStatus_offset;
int jdk_internal_vm_ThreadSnapshot::_carrierThread_offset;
int jdk_internal_vm_ThreadSnapshot::_stackTrace_offset;
int jdk_internal_vm_ThreadSnapshot::_locks_offset;
int jdk_internal_vm_ThreadSnapshot::_blockerTypeOrdinal_offset;
int jdk_internal_vm_ThreadSnapshot::_blockerObject_offset;
oop ThreadSnapshotFactory::get_thread_snapshot(jobject jthread, TRAPS) {
ThreadsListHandle tlh(THREAD);
ResourceMark rm(THREAD);
HandleMark hm(THREAD);
Handle thread_h(THREAD, JNIHandles::resolve(jthread));
// wrapper to auto delete JvmtiVTMSTransitionDisabler
class TransitionDisabler {
JvmtiVTMSTransitionDisabler* _transition_disabler;
public:
TransitionDisabler(): _transition_disabler(nullptr) {}
~TransitionDisabler() {
reset();
}
void init(jobject jthread) {
_transition_disabler = new (mtInternal) JvmtiVTMSTransitionDisabler(jthread);
}
void reset() {
if (_transition_disabler != nullptr) {
delete _transition_disabler;
_transition_disabler = nullptr;
}
}
} transition_disabler;
JavaThread* java_thread = nullptr;
bool is_virtual = java_lang_VirtualThread::is_instance(thread_h());
Handle carrier_thread;
if (is_virtual) {
// 1st need to disable mount/unmount transitions
transition_disabler.init(jthread);
carrier_thread = Handle(THREAD, java_lang_VirtualThread::carrier_thread(thread_h()));
if (carrier_thread != nullptr) {
java_thread = java_lang_Thread::thread(carrier_thread());
}
} else {
java_thread = java_lang_Thread::thread(thread_h());
}
// Handshake with target
GetThreadSnapshotClosure cl(thread_h, java_thread);
if (java_thread == nullptr) {
// unmounted vthread, execute on the current thread
cl.do_thread(nullptr);
} else {
Handshake::execute(&cl, &tlh, java_thread);
}
// all info is collected, can enable transitions.
transition_disabler.reset();
// StackTrace
InstanceKlass* ste_klass = vmClasses::StackTraceElement_klass();
assert(ste_klass != nullptr, "must be loaded");
objArrayHandle trace = oopFactory::new_objArray_handle(ste_klass, cl._frame_count, CHECK_NULL);
for (int i = 0; i < cl._frame_count; i++) {
methodHandle method(THREAD, cl._methods->at(i));
oop element = java_lang_StackTraceElement::create(method, cl._bcis->at(i), CHECK_NULL);
trace->obj_at_put(i, element);
}
// Locks
Symbol* lock_sym = vmSymbols::jdk_internal_vm_ThreadLock();
Klass* lock_k = SystemDictionary::resolve_or_fail(lock_sym, true, CHECK_NULL);
InstanceKlass* lock_klass = InstanceKlass::cast(lock_k);
objArrayHandle locks;
if (cl._locks != nullptr && cl._locks->length() > 0) {
locks = oopFactory::new_objArray_handle(lock_klass, cl._locks->length(), CHECK_NULL);
for (int n = 0; n < cl._locks->length(); n++) {
GetThreadSnapshotClosure::OwnedLock* lock_info = cl._locks->adr_at(n);
Handle lock = jdk_internal_vm_ThreadLock::create(lock_klass,
lock_info->_frame_depth, lock_info->_type, lock_info->_obj, CHECK_NULL);
locks->obj_at_put(n, lock());
}
}
// call static StackTraceElement[] StackTraceElement.of(StackTraceElement[] stackTrace)
// to properly initialize STEs.
JavaValue result(T_OBJECT);
JavaCalls::call_static(&result,
ste_klass,
vmSymbols::java_lang_StackTraceElement_of_name(),
vmSymbols::java_lang_StackTraceElement_of_signature(),
trace,
CHECK_NULL);
// the method return the same trace array
Symbol* snapshot_klass_name = vmSymbols::jdk_internal_vm_ThreadSnapshot();
Klass* snapshot_klass = SystemDictionary::resolve_or_fail(snapshot_klass_name, true, CHECK_NULL);
if (snapshot_klass->should_be_initialized()) {
snapshot_klass->initialize(CHECK_NULL);
}
Handle snapshot = jdk_internal_vm_ThreadSnapshot::allocate(InstanceKlass::cast(snapshot_klass), CHECK_NULL);
jdk_internal_vm_ThreadSnapshot::set_name(snapshot(), cl._thread_name.resolve());
jdk_internal_vm_ThreadSnapshot::set_thread_status(snapshot(), (int)cl._thread_status);
jdk_internal_vm_ThreadSnapshot::set_carrier_thread(snapshot(), carrier_thread());
jdk_internal_vm_ThreadSnapshot::set_stack_trace(snapshot(), trace());
jdk_internal_vm_ThreadSnapshot::set_locks(snapshot(), locks());
if (!cl._blocker.is_empty()) {
jdk_internal_vm_ThreadSnapshot::set_blocker(snapshot(), cl._blocker._type, cl._blocker._obj.resolve());
}
return snapshot();
}
#endif // INCLUDE_JVMTI

View File

@ -631,4 +631,11 @@ class JavaThreadSleepState : public JavaThreadStatusChanger {
} }
}; };
// jdk.internal.vm.ThreadSnapshot support
class ThreadSnapshotFactory: AllStatic {
public:
JVMTI_ONLY(static oop get_thread_snapshot(jobject jthread, TRAPS);)
};
#endif // SHARE_SERVICES_THREADSERVICE_HPP #endif // SHARE_SERVICES_THREADSERVICE_HPP

View File

@ -0,0 +1,217 @@
/*
* Copyright (c) 2025, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.internal.vm;
import java.util.Arrays;
import java.util.stream.Stream;
/**
* Represents a snapshot of information about a Thread.
*/
class ThreadSnapshot {
private static final StackTraceElement[] EMPTY_STACK = new StackTraceElement[0];
private static final ThreadLock[] EMPTY_LOCKS = new ThreadLock[0];
// filled by VM
private String name;
private int threadStatus;
private Thread carrierThread;
private StackTraceElement[] stackTrace;
// owned monitors
private ThreadLock[] locks;
// an object the thread is blocked/waiting on, converted to ThreadBlocker by ThreadSnapshot.of()
private int blockerTypeOrdinal;
private Object blockerObject;
// set by ThreadSnapshot.of()
private ThreadBlocker blocker;
private ThreadSnapshot() {}
/**
* Take a snapshot of a Thread to get all information about the thread.
*/
static ThreadSnapshot of(Thread thread) {
ThreadSnapshot snapshot = create(thread);
if (snapshot.stackTrace == null) {
snapshot.stackTrace = EMPTY_STACK;
}
if (snapshot.locks != null) {
Arrays.stream(snapshot.locks).forEach(ThreadLock::finishInit);
} else {
snapshot.locks = EMPTY_LOCKS;
}
if (snapshot.blockerObject != null) {
snapshot.blocker = new ThreadBlocker(snapshot.blockerTypeOrdinal, snapshot.blockerObject);
snapshot.blockerObject = null; // release
}
return snapshot;
}
/**
* Returns the thread name.
*/
String threadName() {
return name;
}
/**
* Returns the thread state.
*/
Thread.State threadState() {
return jdk.internal.misc.VM.toThreadState(threadStatus);
}
/**
* Returns the thread stack trace.
*/
StackTraceElement[] stackTrace() {
return stackTrace;
}
/**
* Returns the thread's parkBlocker.
*/
Object parkBlocker() {
return getBlocker(BlockerLockType.PARK_BLOCKER);
}
/**
* Returns the object that the thread is blocked on.
* @throws IllegalStateException if not in the blocked state
*/
Object blockedOn() {
if (threadState() != Thread.State.BLOCKED) {
throw new IllegalStateException();
}
return getBlocker(BlockerLockType.WAITING_TO_LOCK);
}
/**
* Returns the object that the thread is waiting on.
* @throws IllegalStateException if not in the waiting state
*/
Object waitingOn() {
if (threadState() != Thread.State.WAITING
&& threadState() != Thread.State.TIMED_WAITING) {
throw new IllegalStateException();
}
return getBlocker(BlockerLockType.WAITING_ON);
}
private Object getBlocker(BlockerLockType type) {
return (blocker != null && blocker.type == type) ? blocker.obj : null;
}
/**
* Returns true if the thread owns any object monitors.
*/
boolean ownsMonitors() {
return locks.length > 0;
}
/**
* Returns the objects that the thread locked at the given depth. The stream
* will contain a null element for a monitor that has been eliminated.
*/
Stream<Object> ownedMonitorsAt(int depth) {
return Arrays.stream(locks)
.filter(lock -> lock.depth() == depth)
.map(lock -> (lock.type == OwnedLockType.LOCKED)
? lock.lockObject()
: /*eliminated*/ null);
}
/**
* If the thread is a mounted virtual thread then return its carrier.
*/
Thread carrierThread() {
return carrierThread;
}
/**
* Represents information about a locking operation.
*/
private enum OwnedLockType {
LOCKED,
// Lock object is a class of the eliminated monitor
ELIMINATED,
}
private enum BlockerLockType {
// Park blocker
PARK_BLOCKER,
WAITING_TO_LOCK,
// Object.wait()
WAITING_ON,
}
/**
* Represents a locking operation of a thread at a specific stack depth.
*/
private class ThreadLock {
private static final OwnedLockType[] lockTypeValues = OwnedLockType.values(); // cache
// set by the VM
private int depth;
private int typeOrdinal;
private Object obj;
// set by ThreadLock.of()
private OwnedLockType type;
private ThreadLock() {}
void finishInit() {
type = lockTypeValues[typeOrdinal];
}
int depth() {
return depth;
}
OwnedLockType type() {
return type;
}
Object lockObject() {
if (type == OwnedLockType.ELIMINATED) {
// we have no lock object, lock contains lock class
return null;
}
return obj;
}
}
private record ThreadBlocker(BlockerLockType type, Object obj) {
private static final BlockerLockType[] lockTypeValues = BlockerLockType.values(); // cache
ThreadBlocker(int typeOrdinal, Object obj) {
this(lockTypeValues[typeOrdinal], obj);
}
}
private static native ThreadSnapshot create(Thread thread);
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) 2025, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.
*/
#include "jni.h"
#include "jvm.h"
#include "jdk_internal_vm_ThreadSnapshot.h"
JNIEXPORT jobject JNICALL
Java_jdk_internal_vm_ThreadSnapshot_create(JNIEnv *env, jclass cls, jobject thread)
{
return JVM_CreateThreadSnapshot(env, thread);
}