diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index 3b34edf416e..8d32d73fb47 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -1872,7 +1872,7 @@ ByteSize java_lang_Thread::thread_id_offset() { } oop java_lang_Thread::park_blocker(oop java_thread) { - return java_thread->obj_field(_park_blocker_offset); + return java_thread->obj_field_access(_park_blocker_offset); } oop java_lang_Thread::async_get_stack_trace(oop java_thread, TRAPS) { diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index f9f6bd07254..dc9ce61627b 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -742,6 +742,12 @@ class SerializeClosure; template(jdk_internal_vm_ThreadDumper, "jdk/internal/vm/ThreadDumper") \ template(dumpThreads_name, "dumpThreads") \ 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 */ \ template(jdk_internal_vm_JcmdVThreadCommands, "jdk/internal/vm/JcmdVThreadCommands") \ diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index 61857ac3ccd..73f60765a70 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -301,6 +301,9 @@ JVM_HoldsLock(JNIEnv *env, jclass threadClass, jobject obj); JNIEXPORT jobject JNICALL JVM_GetStackTrace(JNIEnv *env, jobject thread); +JNIEXPORT jobject JNICALL +JVM_CreateThreadSnapshot(JNIEnv* env, jobject thread); + JNIEXPORT jobjectArray JNICALL JVM_GetAllThreads(JNIEnv *env, jclass dummy); diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index e50318c90b6..d669d7bf5ec 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -2961,6 +2961,15 @@ JVM_ENTRY(jobject, JVM_GetStackTrace(JNIEnv *env, jobject jthread)) return JNIHandles::make_local(THREAD, trace); 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)) // We don't use a ThreadsListHandle here because the current thread // must be alive. diff --git a/src/hotspot/share/prims/jvmtiThreadState.hpp b/src/hotspot/share/prims/jvmtiThreadState.hpp index cb0a779bd5a..62d0c45e337 100644 --- a/src/hotspot/share/prims/jvmtiThreadState.hpp +++ b/src/hotspot/share/prims/jvmtiThreadState.hpp @@ -77,7 +77,7 @@ class JvmtiEnvThreadStateIterator : public StackObj { // // Virtual Thread Mount State Transition (VTMS transition) mechanism // -class JvmtiVTMSTransitionDisabler { +class JvmtiVTMSTransitionDisabler : public AnyObj { 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_all_count; // transitions for all virtual threads are disabled while it is positive diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp index 26a762fa109..8b6fa392c95 100644 --- a/src/hotspot/share/services/diagnosticCommand.cpp +++ b/src/hotspot/share/services/diagnosticCommand.cpp @@ -127,7 +127,9 @@ void DCmd::register_dcmds(){ DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); #endif // INCLUDE_JVMTI DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); +#if INCLUDE_JVMTI DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); +#endif // INCLUDE_JVMTI DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export, true, false)); diff --git a/src/hotspot/share/services/threadService.cpp b/src/hotspot/share/services/threadService.cpp index d320e17fafb..8e0c955bff8 100644 --- a/src/hotspot/share/services/threadService.cpp +++ b/src/hotspot/share/services/threadService.cpp @@ -34,21 +34,25 @@ #include "nmt/memTag.hpp" #include "oops/instanceKlass.hpp" #include "oops/klass.inline.hpp" +#include "oops/method.inline.hpp" #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" #include "oops/oopHandle.inline.hpp" #include "prims/jvmtiRawMonitor.hpp" +#include "prims/jvmtiThreadState.hpp" #include "runtime/atomic.hpp" #include "runtime/handles.inline.hpp" #include "runtime/init.hpp" +#include "runtime/javaCalls.hpp" #include "runtime/javaThread.inline.hpp" +#include "runtime/jniHandles.inline.hpp" #include "runtime/objectMonitor.inline.hpp" -#include "runtime/synchronizer.hpp" +#include "runtime/synchronizer.inline.hpp" #include "runtime/thread.inline.hpp" #include "runtime/threads.hpp" #include "runtime/threadSMR.inline.hpp" -#include "runtime/vframe.hpp" +#include "runtime/vframe.inline.hpp" #include "runtime/vmThread.hpp" #include "runtime/vmOperations.hpp" #include "services/threadService.hpp" @@ -1115,3 +1119,431 @@ ThreadsListEnumerator::ThreadsListEnumerator(Thread* cur_thread, _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* _methods; + GrowableArray* _bcis; + JavaThreadStatus _thread_status; + OopHandle _thread_name; + GrowableArray* _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* 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(init_length, mtInternal); + _bcis = new (mtInternal) GrowableArray(init_length, mtInternal); + _locks = new (mtInternal) GrowableArray(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 + diff --git a/src/hotspot/share/services/threadService.hpp b/src/hotspot/share/services/threadService.hpp index 17d8ba9b3cc..b1de4bc8703 100644 --- a/src/hotspot/share/services/threadService.hpp +++ b/src/hotspot/share/services/threadService.hpp @@ -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 diff --git a/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java b/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java new file mode 100644 index 00000000000..205bfde5449 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/vm/ThreadSnapshot.java @@ -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 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); +} diff --git a/src/java.base/share/native/libjava/ThreadSnapshot.c b/src/java.base/share/native/libjava/ThreadSnapshot.c new file mode 100644 index 00000000000..feea629f4c7 --- /dev/null +++ b/src/java.base/share/native/libjava/ThreadSnapshot.c @@ -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); +}