diff --git a/make/data/hotspot-symbols/symbols-unix b/make/data/hotspot-symbols/symbols-unix index ae49a992f54..0870a8c2187 100644 --- a/make/data/hotspot-symbols/symbols-unix +++ b/make/data/hotspot-symbols/symbols-unix @@ -218,10 +218,8 @@ JVM_DefineModule JVM_SetBootLoaderUnnamedModule # Virtual thread notifications for JVMTI -JVM_VirtualThreadMountBegin -JVM_VirtualThreadMountEnd -JVM_VirtualThreadUnmountBegin -JVM_VirtualThreadUnmountEnd +JVM_VirtualThreadMount +JVM_VirtualThreadUnmount JVM_VirtualThreadHideFrames # Scoped values diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index 98c75cfba39..b1899f1a693 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -1933,34 +1933,23 @@ void java_lang_ThreadGroup::serialize_offsets(SerializeClosure* f) { // java_lang_VirtualThread -int java_lang_VirtualThread::static_notify_jvmti_events_offset; int java_lang_VirtualThread::static_vthread_scope_offset; int java_lang_VirtualThread::_carrierThread_offset; int java_lang_VirtualThread::_continuation_offset; int java_lang_VirtualThread::_state_offset; #define VTHREAD_FIELDS_DO(macro) \ - macro(static_notify_jvmti_events_offset, k, "notifyJvmtiEvents", bool_signature, true); \ macro(static_vthread_scope_offset, k, "VTHREAD_SCOPE", continuationscope_signature, true); \ macro(_carrierThread_offset, k, "carrierThread", thread_signature, false); \ macro(_continuation_offset, k, "cont", continuation_signature, false); \ macro(_state_offset, k, "state", int_signature, false) -static bool vthread_notify_jvmti_events = JNI_FALSE; void java_lang_VirtualThread::compute_offsets() { InstanceKlass* k = vmClasses::VirtualThread_klass(); VTHREAD_FIELDS_DO(FIELD_COMPUTE_OFFSET); } -void java_lang_VirtualThread::init_static_notify_jvmti_events() { - if (vthread_notify_jvmti_events) { - InstanceKlass* ik = vmClasses::VirtualThread_klass(); - oop base = ik->static_field_base_raw(); - base->release_bool_field_put(static_notify_jvmti_events_offset, vthread_notify_jvmti_events); - } -} - bool java_lang_VirtualThread::is_instance(oop obj) { return obj != nullptr && is_subclass(obj->klass()); } @@ -2013,15 +2002,6 @@ void java_lang_VirtualThread::serialize_offsets(SerializeClosure* f) { } #endif -bool java_lang_VirtualThread::notify_jvmti_events() { - return vthread_notify_jvmti_events == JNI_TRUE; -} - -void java_lang_VirtualThread::set_notify_jvmti_events(bool enable) { - vthread_notify_jvmti_events = enable; -} - - // java_lang_Throwable int java_lang_Throwable::_backtrace_offset; @@ -5387,6 +5367,5 @@ int InjectedField::compute_offset() { void javaClasses_init() { JavaClasses::compute_offsets(); JavaClasses::check_offsets(); - java_lang_VirtualThread::init_static_notify_jvmti_events(); FilteredFieldsMap::initialize(); // must be done after computing offsets. } diff --git a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp index f4f81420757..6e36f68f671 100644 --- a/src/hotspot/share/classfile/javaClasses.hpp +++ b/src/hotspot/share/classfile/javaClasses.hpp @@ -510,7 +510,6 @@ class java_lang_ThreadGroup : AllStatic { class java_lang_VirtualThread : AllStatic { private: - static int static_notify_jvmti_events_offset; static int static_vthread_scope_offset; static int _carrierThread_offset; static int _continuation_offset; @@ -548,9 +547,6 @@ class java_lang_VirtualThread : AllStatic { static oop continuation(oop vthread); static int state(oop vthread); static JavaThreadStatus map_state_to_thread_status(int state); - static bool notify_jvmti_events(); - static void set_notify_jvmti_events(bool enable); - static void init_static_notify_jvmti_events(); }; diff --git a/src/hotspot/share/classfile/vmIntrinsics.hpp b/src/hotspot/share/classfile/vmIntrinsics.hpp index c72d95203ba..f2f1e2f9cac 100644 --- a/src/hotspot/share/classfile/vmIntrinsics.hpp +++ b/src/hotspot/share/classfile/vmIntrinsics.hpp @@ -583,6 +583,11 @@ class methodHandle; do_intrinsic(_Continuation_doYield, jdk_internal_vm_Continuation, doYield_name, continuationDoYield_signature, F_SN) \ do_alias( continuationDoYield_signature, void_int_signature) \ \ + /* java/lang/VirtualThread */ \ + do_intrinsic(_notifyJvmtiMount, java_lang_VirtualThread, notifyJvmtiMount_name, bool_bool_void_signature, F_RN) \ + do_intrinsic(_notifyJvmtiUnmount, java_lang_VirtualThread, notifyJvmtiUnmount_name, bool_bool_void_signature, F_RN) \ + do_intrinsic(_notifyJvmtiHideFrames, java_lang_VirtualThread, notifyJvmtiHideFrames_name, bool_void_signature, F_RN) \ + \ /* support for UnsafeConstants */ \ do_class(jdk_internal_misc_UnsafeConstants, "jdk/internal/misc/UnsafeConstants") \ \ diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 17d1ffdbe92..caf6788c028 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -412,6 +412,9 @@ template(run_finalization_name, "runFinalization") \ template(dispatchUncaughtException_name, "dispatchUncaughtException") \ template(loadClass_name, "loadClass") \ + template(notifyJvmtiMount_name, "notifyJvmtiMount") \ + template(notifyJvmtiUnmount_name, "notifyJvmtiUnmount") \ + template(notifyJvmtiHideFrames_name, "notifyJvmtiHideFrames") \ template(doYield_name, "doYield") \ template(enter_name, "enter") \ template(enterSpecial_name, "enterSpecial") \ @@ -550,6 +553,7 @@ template(void_float_signature, "()F") \ template(void_double_signature, "()D") \ template(bool_void_signature, "(Z)V") \ + template(bool_bool_void_signature, "(ZZ)V") \ template(int_void_signature, "(I)V") \ template(int_int_signature, "(I)I") \ template(char_char_signature, "(C)C") \ diff --git a/src/hotspot/share/include/jvm.h b/src/hotspot/share/include/jvm.h index 98e0233887c..defb99f0406 100644 --- a/src/hotspot/share/include/jvm.h +++ b/src/hotspot/share/include/jvm.h @@ -1144,16 +1144,10 @@ JVM_GetEnclosingMethodInfo(JNIEnv* env, jclass ofClass); * Virtual thread support. */ JNIEXPORT void JNICALL -JVM_VirtualThreadMountBegin(JNIEnv* env, jobject vthread, jboolean first_mount); +JVM_VirtualThreadMount(JNIEnv* env, jobject vthread, jboolean hide, jboolean first_mount); JNIEXPORT void JNICALL -JVM_VirtualThreadMountEnd(JNIEnv* env, jobject vthread, jboolean first_mount); - -JNIEXPORT void JNICALL -JVM_VirtualThreadUnmountBegin(JNIEnv* env, jobject vthread, jboolean last_unmount); - -JNIEXPORT void JNICALL -JVM_VirtualThreadUnmountEnd(JNIEnv* env, jobject vthread, jboolean last_unmount); +JVM_VirtualThreadUnmount(JNIEnv* env, jobject vthread, jboolean hide, jboolean last_unmount); JNIEXPORT void JNICALL JVM_VirtualThreadHideFrames(JNIEnv* env, jobject vthread, jboolean hide); diff --git a/src/hotspot/share/opto/c2compiler.cpp b/src/hotspot/share/opto/c2compiler.cpp index f5bfb06d2e7..a27e65402d1 100644 --- a/src/hotspot/share/opto/c2compiler.cpp +++ b/src/hotspot/share/opto/c2compiler.cpp @@ -771,6 +771,11 @@ bool C2Compiler::is_intrinsic_supported(const methodHandle& method) { case vmIntrinsics::_IndexPartiallyInUpperRange: return EnableVectorSupport; case vmIntrinsics::_blackhole: +#if INCLUDE_JVMTI + case vmIntrinsics::_notifyJvmtiMount: + case vmIntrinsics::_notifyJvmtiUnmount: + case vmIntrinsics::_notifyJvmtiHideFrames: +#endif break; default: diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 28bde8e4df7..5093dcf2503 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -50,6 +50,7 @@ #include "opto/runtime.hpp" #include "opto/rootnode.hpp" #include "opto/subnode.hpp" +#include "prims/jvmtiThreadState.hpp" #include "prims/unsafe.hpp" #include "runtime/jniHandles.inline.hpp" #include "runtime/objectMonitor.hpp" @@ -478,6 +479,14 @@ bool LibraryCallKit::try_to_inline(int predicate) { case vmIntrinsics::_scopedValueCache: return inline_native_scopedValueCache(); case vmIntrinsics::_setScopedValueCache: return inline_native_setScopedValueCache(); +#if INCLUDE_JVMTI + case vmIntrinsics::_notifyJvmtiMount: return inline_native_notify_jvmti_funcs(CAST_FROM_FN_PTR(address, OptoRuntime::notify_jvmti_mount()), + "notifyJvmtiMount"); + case vmIntrinsics::_notifyJvmtiUnmount: return inline_native_notify_jvmti_funcs(CAST_FROM_FN_PTR(address, OptoRuntime::notify_jvmti_unmount()), + "notifyJvmtiUnmount"); + case vmIntrinsics::_notifyJvmtiHideFrames: return inline_native_notify_jvmti_hide(); +#endif + #ifdef JFR_HAVE_INTRINSICS case vmIntrinsics::_counterTime: return inline_native_time_funcs(CAST_FROM_FN_PTR(address, JfrTime::time_function()), "counterTime"); case vmIntrinsics::_getEventWriter: return inline_native_getEventWriter(); @@ -2841,6 +2850,75 @@ bool LibraryCallKit::inline_native_time_funcs(address funcAddr, const char* func return true; } + +#if INCLUDE_JVMTI + +// When notifications are disabled then just update the VTMS transition bit and return. +// Otherwise, the bit is updated in the given function call implementing JVMTI notification protocol. +bool LibraryCallKit::inline_native_notify_jvmti_funcs(address funcAddr, const char* funcName) { + if (!DoJVMTIVirtualThreadTransitions) { + return true; + } + IdealKit ideal(this); + + Node* ONE = ideal.ConI(1); + Node* hide = _gvn.transform(argument(1)); // hide argument: true for begin and false for end of VTMS transition + Node* addr = makecon(TypeRawPtr::make((address)&JvmtiVTMSTransitionDisabler::_VTMS_notify_jvmti_events)); + Node* notify_jvmti_enabled = ideal.load(ideal.ctrl(), addr, TypeInt::BOOL, T_BOOLEAN, Compile::AliasIdxRaw); + + ideal.if_then(notify_jvmti_enabled, BoolTest::eq, ONE); { + // if notifyJvmti enabled then make a call to the given SharedRuntime function + const TypeFunc* tf = OptoRuntime::notify_jvmti_Type(); + Node* vt_oop = _gvn.transform(must_be_not_null(argument(0), true)); // VirtualThread this argument + Node* cond = _gvn.transform(argument(2)); // firstMount or lastUnmount argument + + sync_kit(ideal); + make_runtime_call(RC_NO_LEAF, tf, funcAddr, funcName, TypePtr::BOTTOM, vt_oop, hide, cond); + ideal.sync_kit(this); + } ideal.else_(); { + // set hide value to the VTMS transition bit in current JavaThread + Node* thread = ideal.thread(); + Node* addr = basic_plus_adr(thread, in_bytes(JavaThread::is_in_VTMS_transition_offset())); + const TypePtr *addr_type = _gvn.type(addr)->isa_ptr(); + + sync_kit(ideal); + access_store_at(nullptr, addr, addr_type, hide, _gvn.type(hide), T_BOOLEAN, IN_NATIVE | MO_UNORDERED); + ideal.sync_kit(this); + } ideal.end_if(); + final_sync(ideal); + + return true; +} + +// If notifications are enabled then just update the temporary VTMS transition bit. +bool LibraryCallKit::inline_native_notify_jvmti_hide() { + if (!DoJVMTIVirtualThreadTransitions) { + return true; + } + IdealKit ideal(this); + + Node* ONE = ideal.ConI(1); + Node* addr = makecon(TypeRawPtr::make((address)&JvmtiVTMSTransitionDisabler::_VTMS_notify_jvmti_events)); + Node* notify_jvmti_enabled = ideal.load(ideal.ctrl(), addr, TypeInt::BOOL, T_BOOLEAN, Compile::AliasIdxRaw); + + ideal.if_then(notify_jvmti_enabled, BoolTest::eq, ONE); { + // set the VTMS temporary transition bit in current JavaThread + Node* thread = ideal.thread(); + Node* hide = _gvn.transform(argument(1)); // hide argument for temporary VTMS transition notification + Node* addr = basic_plus_adr(thread, in_bytes(JavaThread::is_in_tmp_VTMS_transition_offset())); + const TypePtr *addr_type = _gvn.type(addr)->isa_ptr(); + + sync_kit(ideal); + access_store_at(nullptr, addr, addr_type, hide, _gvn.type(hide), T_BOOLEAN, IN_NATIVE | MO_UNORDERED); + ideal.sync_kit(this); + } ideal.end_if(); + final_sync(ideal); + + return true; +} + +#endif // INCLUDE_JVMTI + #ifdef JFR_HAVE_INTRINSICS /** diff --git a/src/hotspot/share/opto/library_call.hpp b/src/hotspot/share/opto/library_call.hpp index dc7884cf139..457b0569182 100644 --- a/src/hotspot/share/opto/library_call.hpp +++ b/src/hotspot/share/opto/library_call.hpp @@ -245,6 +245,11 @@ class LibraryCallKit : public GraphKit { bool inline_native_setScopedValueCache(); bool inline_native_time_funcs(address method, const char* funcName); +#if INCLUDE_JVMTI + bool inline_native_notify_jvmti_funcs(address funcAddr, const char* funcName); + bool inline_native_notify_jvmti_hide(); +#endif + #ifdef JFR_HAVE_INTRINSICS bool inline_native_classID(); bool inline_native_getEventWriter(); diff --git a/src/hotspot/share/opto/runtime.cpp b/src/hotspot/share/opto/runtime.cpp index e60dc00ea9d..5434d64e850 100644 --- a/src/hotspot/share/opto/runtime.cpp +++ b/src/hotspot/share/opto/runtime.cpp @@ -109,6 +109,10 @@ address OptoRuntime::_rethrow_Java = nullptr; address OptoRuntime::_slow_arraycopy_Java = nullptr; address OptoRuntime::_register_finalizer_Java = nullptr; +#if INCLUDE_JVMTI +address OptoRuntime::_notify_jvmti_mount = nullptr; +address OptoRuntime::_notify_jvmti_unmount = nullptr; +#endif ExceptionBlob* OptoRuntime::_exception_blob; @@ -148,6 +152,10 @@ bool OptoRuntime::generate(ciEnv* env) { gen(env, _multianewarray4_Java , multianewarray4_Type , multianewarray4_C , 0 , true, false); gen(env, _multianewarray5_Java , multianewarray5_Type , multianewarray5_C , 0 , true, false); gen(env, _multianewarrayN_Java , multianewarrayN_Type , multianewarrayN_C , 0 , true, false); +#if INCLUDE_JVMTI + gen(env, _notify_jvmti_mount , notify_jvmti_Type , SharedRuntime::notify_jvmti_mount, 0 , true, false); + gen(env, _notify_jvmti_unmount , notify_jvmti_Type , SharedRuntime::notify_jvmti_unmount, 0 , true, false); +#endif gen(env, _complete_monitor_locking_Java , complete_monitor_enter_Type , SharedRuntime::complete_monitor_locking_C, 0, false, false); gen(env, _monitor_notify_Java , monitor_notify_Type , monitor_notify_C , 0 , false, false); gen(env, _monitor_notifyAll_Java , monitor_notify_Type , monitor_notifyAll_C , 0 , false, false); @@ -1644,6 +1652,24 @@ const TypeFunc *OptoRuntime::class_id_load_barrier_Type() { } #endif +#if INCLUDE_JVMTI +const TypeFunc *OptoRuntime::notify_jvmti_Type() { + // create input type (domain) + const Type **fields = TypeTuple::fields(3); + fields[TypeFunc::Parms+0] = TypeInstPtr::NOTNULL; // VirtualThread oop + fields[TypeFunc::Parms+1] = TypeInt::BOOL; // jboolean + fields[TypeFunc::Parms+2] = TypeInt::BOOL; // jboolean + const TypeTuple *domain = TypeTuple::make(TypeFunc::Parms+3,fields); + + // no result type needed + fields = TypeTuple::fields(1); + fields[TypeFunc::Parms+0] = NULL; // void + const TypeTuple* range = TypeTuple::make(TypeFunc::Parms, fields); + + return TypeFunc::make(domain,range); +} +#endif + //----------------------------------------------------------------------------- // Dtrace support. entry and exit probes have the same signature const TypeFunc *OptoRuntime::dtrace_method_entry_exit_Type() { diff --git a/src/hotspot/share/opto/runtime.hpp b/src/hotspot/share/opto/runtime.hpp index 53bb3737902..f1093a02093 100644 --- a/src/hotspot/share/opto/runtime.hpp +++ b/src/hotspot/share/opto/runtime.hpp @@ -135,6 +135,10 @@ class OptoRuntime : public AllStatic { static address _slow_arraycopy_Java; static address _register_finalizer_Java; +#if INCLUDE_JVMTI + static address _notify_jvmti_mount; + static address _notify_jvmti_unmount; +#endif // // Implementation of runtime methods @@ -208,6 +212,10 @@ private: static address slow_arraycopy_Java() { return _slow_arraycopy_Java; } static address register_finalizer_Java() { return _register_finalizer_Java; } +#if INCLUDE_JVMTI + static address notify_jvmti_mount() { return _notify_jvmti_mount; } + static address notify_jvmti_unmount() { return _notify_jvmti_unmount; } +#endif static ExceptionBlob* exception_blob() { return _exception_blob; } @@ -294,6 +302,9 @@ private: static const TypeFunc* register_finalizer_Type(); JFR_ONLY(static const TypeFunc* class_id_load_barrier_Type();) +#if INCLUDE_JVMTI + static const TypeFunc* notify_jvmti_Type(); +#endif // Dtrace support static const TypeFunc* dtrace_method_entry_exit_Type(); diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index ade54717d49..25a1835cd6c 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -3917,123 +3917,60 @@ JVM_LEAF(jint, JVM_FindSignal(const char *name)) return os::get_signal_number(name); JVM_END -JVM_ENTRY(void, JVM_VirtualThreadMountBegin(JNIEnv* env, jobject vthread, jboolean first_mount)) +// If notifications are disabled then just update the VTMS transition bit and return. +// Otherwise, the bit is updated in the given jvmtiVTMSTransitionDisabler function call. +JVM_ENTRY(void, JVM_VirtualThreadMount(JNIEnv* env, jobject vthread, jboolean hide, jboolean first_mount)) #if INCLUDE_JVMTI if (!DoJVMTIVirtualThreadTransitions) { assert(!JvmtiExport::can_support_virtual_threads(), "sanity check"); return; } - assert(!thread->is_in_tmp_VTMS_transition(), "sanity check"); - assert(!thread->is_in_VTMS_transition(), "sanity check"); - JvmtiVTMSTransitionDisabler::start_VTMS_transition(vthread, /* is_mount */ true); -#else - fatal("Should only be called with JVMTI enabled"); -#endif -JVM_END - -JVM_ENTRY(void, JVM_VirtualThreadMountEnd(JNIEnv* env, jobject vthread, jboolean first_mount)) -#if INCLUDE_JVMTI - if (!DoJVMTIVirtualThreadTransitions) { - assert(!JvmtiExport::can_support_virtual_threads(), "sanity check"); + if (!JvmtiVTMSTransitionDisabler::VTMS_notify_jvmti_events()) { + thread->set_is_in_VTMS_transition(hide); return; } - oop vt = JNIHandles::resolve(vthread); - - thread->rebind_to_jvmti_thread_state_of(vt); - - { - MutexLocker mu(JvmtiThreadState_lock); - JvmtiThreadState* state = thread->jvmti_thread_state(); - if (state != nullptr && state->is_pending_interp_only_mode()) { - JvmtiEventController::enter_interp_only_mode(); - } - } - assert(thread->is_in_VTMS_transition(), "sanity check"); - assert(!thread->is_in_tmp_VTMS_transition(), "sanity check"); - JvmtiVTMSTransitionDisabler::finish_VTMS_transition(vthread, /* is_mount */ true); - if (first_mount) { - // thread start - if (JvmtiExport::can_support_virtual_threads()) { - JvmtiEventController::thread_started(thread); - if (JvmtiExport::should_post_vthread_start()) { - JvmtiExport::post_vthread_start(vthread); - } - } else { // compatibility for vthread unaware agents: legacy thread_start - if (PostVirtualThreadCompatibleLifecycleEvents && - JvmtiExport::should_post_thread_life()) { - // JvmtiEventController::thread_started is called here - JvmtiExport::post_thread_start(thread); - } - } - } - if (JvmtiExport::should_post_vthread_mount()) { - JvmtiExport::post_vthread_mount(vthread); + if (hide) { + JvmtiVTMSTransitionDisabler::VTMS_mount_begin(vthread, first_mount); + } else { + JvmtiVTMSTransitionDisabler::VTMS_mount_end(vthread, first_mount); } #else fatal("Should only be called with JVMTI enabled"); #endif JVM_END -JVM_ENTRY(void, JVM_VirtualThreadUnmountBegin(JNIEnv* env, jobject vthread, jboolean last_unmount)) +// If notifications are disabled then just update the VTMS transition bit and return. +// Otherwise, the bit is updated in the given jvmtiVTMSTransitionDisabler function call below. +JVM_ENTRY(void, JVM_VirtualThreadUnmount(JNIEnv* env, jobject vthread, jboolean hide, jboolean last_unmount)) #if INCLUDE_JVMTI if (!DoJVMTIVirtualThreadTransitions) { assert(!JvmtiExport::can_support_virtual_threads(), "sanity check"); return; } - HandleMark hm(thread); - Handle ct(thread, thread->threadObj()); - - if (JvmtiExport::should_post_vthread_unmount()) { - JvmtiExport::post_vthread_unmount(vthread); - } - if (last_unmount) { - if (JvmtiExport::can_support_virtual_threads()) { - if (JvmtiExport::should_post_vthread_end()) { - JvmtiExport::post_vthread_end(vthread); - } - } else { // compatibility for vthread unaware agents: legacy thread_end - if (PostVirtualThreadCompatibleLifecycleEvents && - JvmtiExport::should_post_thread_life()) { - JvmtiExport::post_thread_end(thread); - } - } - } - assert(!thread->is_in_tmp_VTMS_transition(), "sanity check"); - assert(!thread->is_in_VTMS_transition(), "sanity check"); - JvmtiVTMSTransitionDisabler::start_VTMS_transition(vthread, /* is_mount */ false); - - if (last_unmount && thread->jvmti_thread_state() != nullptr) { - JvmtiExport::cleanup_thread(thread); - thread->set_jvmti_thread_state(nullptr); - oop vt = JNIHandles::resolve(vthread); - java_lang_Thread::set_jvmti_thread_state(vt, nullptr); - } - thread->rebind_to_jvmti_thread_state_of(ct()); -#else - fatal("Should only be called with JVMTI enabled"); -#endif -JVM_END - -JVM_ENTRY(void, JVM_VirtualThreadUnmountEnd(JNIEnv* env, jobject vthread, jboolean last_unmount)) -#if INCLUDE_JVMTI - if (!DoJVMTIVirtualThreadTransitions) { - assert(!JvmtiExport::can_support_virtual_threads(), "sanity check"); + if (!JvmtiVTMSTransitionDisabler::VTMS_notify_jvmti_events()) { + thread->set_is_in_VTMS_transition(hide); return; } - assert(thread->is_in_VTMS_transition(), "sanity check"); - assert(!thread->is_in_tmp_VTMS_transition(), "sanity check"); - JvmtiVTMSTransitionDisabler::finish_VTMS_transition(vthread, /* is_mount */ false); + if (hide) { + JvmtiVTMSTransitionDisabler::VTMS_unmount_begin(vthread, last_unmount); + } else { + JvmtiVTMSTransitionDisabler::VTMS_unmount_end(vthread, last_unmount); + } #else fatal("Should only be called with JVMTI enabled"); #endif JVM_END +// If notifications are enabled then just update the temporary VTMS transition bit. JVM_ENTRY(void, JVM_VirtualThreadHideFrames(JNIEnv* env, jobject vthread, jboolean hide)) #if INCLUDE_JVMTI if (!DoJVMTIVirtualThreadTransitions) { assert(!JvmtiExport::can_support_virtual_threads(), "sanity check"); return; } + if (!JvmtiVTMSTransitionDisabler::VTMS_notify_jvmti_events()) { + return; + } assert(!thread->is_in_VTMS_transition(), "sanity check"); assert(thread->is_in_tmp_VTMS_transition() != (bool)hide, "sanity check"); thread->toggle_is_in_tmp_VTMS_transition(); diff --git a/src/hotspot/share/prims/jvmtiExport.cpp b/src/hotspot/share/prims/jvmtiExport.cpp index fc2abe52f1e..e508158108d 100644 --- a/src/hotspot/share/prims/jvmtiExport.cpp +++ b/src/hotspot/share/prims/jvmtiExport.cpp @@ -379,11 +379,7 @@ JvmtiExport::get_jvmti_interface(JavaVM *jvm, void **penv, jint version) { } if (Continuations::enabled()) { // Virtual threads support. There is a performance impact when VTMS transitions are enabled. - java_lang_VirtualThread::set_notify_jvmti_events(true); - if (JvmtiEnv::get_phase() == JVMTI_PHASE_LIVE) { - ThreadInVMfromNative __tiv(JavaThread::current()); - java_lang_VirtualThread::init_static_notify_jvmti_events(); - } + JvmtiVTMSTransitionDisabler::set_VTMS_notify_jvmti_events(true); } if (JvmtiEnv::get_phase() == JVMTI_PHASE_LIVE) { diff --git a/src/hotspot/share/prims/jvmtiThreadState.cpp b/src/hotspot/share/prims/jvmtiThreadState.cpp index 0d1646d0569..f33581c6cca 100644 --- a/src/hotspot/share/prims/jvmtiThreadState.cpp +++ b/src/hotspot/share/prims/jvmtiThreadState.cpp @@ -33,7 +33,7 @@ #include "prims/jvmtiThreadState.inline.hpp" #include "runtime/handles.inline.hpp" #include "runtime/interfaceSupport.inline.hpp" -#include "runtime/jniHandles.hpp" +#include "runtime/jniHandles.inline.hpp" #include "runtime/safepointVerifiers.hpp" #include "runtime/stackFrameStream.inline.hpp" #include "runtime/vframe.hpp" @@ -219,12 +219,14 @@ volatile int JvmtiVTMSTransitionDisabler::_VTMS_transition_count = 0; // VTMS transitions for one virtual thread are disabled while it is positive volatile int JvmtiVTMSTransitionDisabler::_VTMS_transition_disable_for_one_count = 0; -// VTMs transitions for all virtual threads are disabled while it is positive +// VTMS transitions for all virtual threads are disabled while it is positive volatile int JvmtiVTMSTransitionDisabler::_VTMS_transition_disable_for_all_count = 0; // There is an active suspender or resumer. volatile bool JvmtiVTMSTransitionDisabler::_SR_mode = false; +// Notifications from VirtualThread about VTMS events are enabled. +bool JvmtiVTMSTransitionDisabler::_VTMS_notify_jvmti_events = false; #ifdef ASSERT void @@ -509,6 +511,94 @@ JvmtiVTMSTransitionDisabler::finish_VTMS_transition(jthread vthread, bool is_mou #endif } +void +JvmtiVTMSTransitionDisabler::VTMS_mount_begin(jobject vthread, jboolean first_mount) { + JavaThread* thread = JavaThread::current(); + assert(!thread->is_in_tmp_VTMS_transition(), "sanity check"); + assert(!thread->is_in_VTMS_transition(), "sanity check"); + start_VTMS_transition(vthread, /* is_mount */ true); +} + +void +JvmtiVTMSTransitionDisabler::VTMS_mount_end(jobject vthread, jboolean first_mount) { + JavaThread* thread = JavaThread::current(); + oop vt = JNIHandles::resolve(vthread); + + thread->rebind_to_jvmti_thread_state_of(vt); + + { + MutexLocker mu(JvmtiThreadState_lock); + JvmtiThreadState* state = thread->jvmti_thread_state(); + if (state != nullptr && state->is_pending_interp_only_mode()) { + JvmtiEventController::enter_interp_only_mode(); + } + } + assert(thread->is_in_VTMS_transition(), "sanity check"); + assert(!thread->is_in_tmp_VTMS_transition(), "sanity check"); + finish_VTMS_transition(vthread, /* is_mount */ true); + if (first_mount) { + // thread start + if (JvmtiExport::can_support_virtual_threads()) { + JvmtiEventController::thread_started(thread); + if (JvmtiExport::should_post_vthread_start()) { + JvmtiExport::post_vthread_start(vthread); + } + } else { // compatibility for vthread unaware agents: legacy thread_start + if (PostVirtualThreadCompatibleLifecycleEvents && + JvmtiExport::should_post_thread_life()) { + // JvmtiEventController::thread_started is called here + JvmtiExport::post_thread_start(thread); + } + } + } + if (JvmtiExport::should_post_vthread_mount()) { + JvmtiExport::post_vthread_mount(vthread); + } +} + +void +JvmtiVTMSTransitionDisabler::VTMS_unmount_begin(jobject vthread, jboolean last_unmount) { + JavaThread* thread = JavaThread::current(); + HandleMark hm(thread); + Handle ct(thread, thread->threadObj()); + + if (JvmtiExport::should_post_vthread_unmount()) { + JvmtiExport::post_vthread_unmount(vthread); + } + if (last_unmount) { + if (JvmtiExport::can_support_virtual_threads()) { + if (JvmtiExport::should_post_vthread_end()) { + JvmtiExport::post_vthread_end(vthread); + } + } else { // compatibility for vthread unaware agents: legacy thread_end + if (PostVirtualThreadCompatibleLifecycleEvents && + JvmtiExport::should_post_thread_life()) { + JvmtiExport::post_thread_end(thread); + } + } + } + assert(!thread->is_in_tmp_VTMS_transition(), "sanity check"); + assert(!thread->is_in_VTMS_transition(), "sanity check"); + start_VTMS_transition(vthread, /* is_mount */ false); + + if (last_unmount && thread->jvmti_thread_state() != nullptr) { + JvmtiExport::cleanup_thread(thread); + thread->set_jvmti_thread_state(nullptr); + oop vt = JNIHandles::resolve(vthread); + java_lang_Thread::set_jvmti_thread_state(vt, nullptr); + } + thread->rebind_to_jvmti_thread_state_of(ct()); +} + +void +JvmtiVTMSTransitionDisabler::VTMS_unmount_end(jobject vthread, jboolean last_unmount) { + JavaThread* thread = JavaThread::current(); + assert(thread->is_in_VTMS_transition(), "sanity check"); + assert(!thread->is_in_tmp_VTMS_transition(), "sanity check"); + finish_VTMS_transition(vthread, /* is_mount */ false); +} + + // // Virtual Threads Suspend/Resume management // diff --git a/src/hotspot/share/prims/jvmtiThreadState.hpp b/src/hotspot/share/prims/jvmtiThreadState.hpp index 1e8bb19c125..69350a52025 100644 --- a/src/hotspot/share/prims/jvmtiThreadState.hpp +++ b/src/hotspot/share/prims/jvmtiThreadState.hpp @@ -94,6 +94,10 @@ class JvmtiVTMSTransitionDisabler { void VTMS_transition_enable_for_all(); public: + static bool _VTMS_notify_jvmti_events; // enable notifications from VirtualThread about VTMS events + static bool VTMS_notify_jvmti_events() { return _VTMS_notify_jvmti_events; } + static void set_VTMS_notify_jvmti_events(bool val) { _VTMS_notify_jvmti_events = val; } + // parameter is_SR: suspender or resumer JvmtiVTMSTransitionDisabler(bool is_SR = false); JvmtiVTMSTransitionDisabler(jthread thread); @@ -101,6 +105,11 @@ class JvmtiVTMSTransitionDisabler { static void start_VTMS_transition(jthread vthread, bool is_mount); static void finish_VTMS_transition(jthread vthread, bool is_mount); + + static void VTMS_mount_begin(jobject vthread, jboolean first_mount); + static void VTMS_mount_end(jobject vthread, jboolean first_mount); + static void VTMS_unmount_begin(jobject vthread, jboolean last_unmount); + static void VTMS_unmount_end(jobject vthread, jboolean last_unmount); }; /////////////////////////////////////////////////////////////// diff --git a/src/hotspot/share/runtime/javaThread.hpp b/src/hotspot/share/runtime/javaThread.hpp index b26b5027a5a..fa7017eb31c 100644 --- a/src/hotspot/share/runtime/javaThread.hpp +++ b/src/hotspot/share/runtime/javaThread.hpp @@ -811,6 +811,11 @@ private: static ByteSize cont_fastpath_offset() { return byte_offset_of(JavaThread, _cont_fastpath); } static ByteSize held_monitor_count_offset() { return byte_offset_of(JavaThread, _held_monitor_count); } +#if INCLUDE_JVMTI + static ByteSize is_in_VTMS_transition_offset() { return byte_offset_of(JavaThread, _is_in_VTMS_transition); } + static ByteSize is_in_tmp_VTMS_transition_offset() { return byte_offset_of(JavaThread, _is_in_tmp_VTMS_transition); } +#endif + // Returns the jni environment for this thread JNIEnv* jni_environment() { return &_jni_environment; } diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index 3c6e5795102..b0d135c63a7 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -53,6 +53,7 @@ #include "oops/oop.inline.hpp" #include "prims/forte.hpp" #include "prims/jvmtiExport.hpp" +#include "prims/jvmtiThreadState.hpp" #include "prims/methodHandles.hpp" #include "prims/nativeLookup.hpp" #include "runtime/atomic.hpp" @@ -62,6 +63,7 @@ #include "runtime/interfaceSupport.inline.hpp" #include "runtime/java.hpp" #include "runtime/javaCalls.hpp" +#include "runtime/jniHandles.hpp" #include "runtime/sharedRuntime.hpp" #include "runtime/stackWatermarkSet.hpp" #include "runtime/stubRoutines.hpp" @@ -623,6 +625,28 @@ void SharedRuntime::throw_and_post_jvmti_exception(JavaThread* current, Symbol* throw_and_post_jvmti_exception(current, h_exception); } +#if INCLUDE_JVMTI +JRT_ENTRY(void, SharedRuntime::notify_jvmti_mount(oopDesc* vt, jboolean hide, jboolean first_mount, JavaThread* current)) + jobject vthread = JNIHandles::make_local(const_cast(vt)); + + if (hide) { + JvmtiVTMSTransitionDisabler::VTMS_mount_begin(vthread, first_mount); + } else { + JvmtiVTMSTransitionDisabler::VTMS_mount_end(vthread, first_mount); + } +JRT_END + +JRT_ENTRY(void, SharedRuntime::notify_jvmti_unmount(oopDesc* vt, jboolean hide, jboolean last_unmount, JavaThread* current)) + jobject vthread = JNIHandles::make_local(const_cast(vt)); + + if (hide) { + JvmtiVTMSTransitionDisabler::VTMS_unmount_begin(vthread, last_unmount); + } else { + JvmtiVTMSTransitionDisabler::VTMS_unmount_end(vthread, last_unmount); + } +JRT_END +#endif // INCLUDE_JVMTI + // The interpreter code to call this tracing function is only // called/generated when UL is on for redefine, class and has the right level // and tags. Since obsolete methods are never compiled, we don't have diff --git a/src/hotspot/share/runtime/sharedRuntime.hpp b/src/hotspot/share/runtime/sharedRuntime.hpp index 31871b9f087..1c951bb939c 100644 --- a/src/hotspot/share/runtime/sharedRuntime.hpp +++ b/src/hotspot/share/runtime/sharedRuntime.hpp @@ -264,6 +264,12 @@ class SharedRuntime: AllStatic { static void throw_and_post_jvmti_exception(JavaThread* current, Handle h_exception); static void throw_and_post_jvmti_exception(JavaThread* current, Symbol* name, const char *message = nullptr); +#if INCLUDE_JVMTI + // Functions for JVMTI notifications + static void notify_jvmti_mount(oopDesc* vt, jboolean hide, jboolean first_mount, JavaThread* current); + static void notify_jvmti_unmount(oopDesc* vt, jboolean hide, jboolean last_unmount, JavaThread* current); +#endif + // RedefineClasses() tracing support for obsolete method entry static int rc_trace_method_entry(JavaThread* thread, Method* m); diff --git a/src/java.base/share/classes/java/lang/VirtualThread.java b/src/java.base/share/classes/java/lang/VirtualThread.java index 1ee253d4d6c..7df8d9da13c 100644 --- a/src/java.base/share/classes/java/lang/VirtualThread.java +++ b/src/java.base/share/classes/java/lang/VirtualThread.java @@ -56,6 +56,7 @@ import jdk.internal.vm.ThreadContainers; import jdk.internal.vm.annotation.ChangesCurrentThread; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Hidden; +import jdk.internal.vm.annotation.IntrinsicCandidate; import jdk.internal.vm.annotation.JvmtiMountTransition; import sun.nio.ch.Interruptible; import sun.security.action.GetPropertyAction; @@ -210,7 +211,7 @@ final class VirtualThread extends BaseVirtualThread { } // notify JVMTI before mount - if (notifyJvmtiEvents) notifyJvmtiMountBegin(firstRun); + notifyJvmtiMount(true, firstRun); try { cont.run(); @@ -287,11 +288,10 @@ final class VirtualThread extends BaseVirtualThread { @ChangesCurrentThread private void run(Runnable task) { assert state == RUNNING; - boolean notifyJvmti = notifyJvmtiEvents; // first mount mount(); - if (notifyJvmti) notifyJvmtiMountEnd(true); + notifyJvmtiMount(false, true); // emit JFR event if enabled if (VirtualThreadStartEvent.isTurnedOn()) { @@ -319,7 +319,7 @@ final class VirtualThread extends BaseVirtualThread { } finally { // last unmount - if (notifyJvmti) notifyJvmtiUnmountBegin(true); + notifyJvmtiUnmount(true, true); unmount(); // final state @@ -381,35 +381,27 @@ final class VirtualThread extends BaseVirtualThread { /** * Sets the current thread to the current carrier thread. - * @return true if JVMTI was notified */ @ChangesCurrentThread @JvmtiMountTransition - private boolean switchToCarrierThread() { - boolean notifyJvmti = notifyJvmtiEvents; - if (notifyJvmti) { - notifyJvmtiHideFrames(true); - } + private void switchToCarrierThread() { + notifyJvmtiHideFrames(true); Thread carrier = this.carrierThread; assert Thread.currentThread() == this && carrier == Thread.currentCarrierThread(); carrier.setCurrentThread(carrier); - return notifyJvmti; } /** * Sets the current thread to the given virtual thread. - * If {@code notifyJvmti} is true then JVMTI is notified. */ @ChangesCurrentThread @JvmtiMountTransition - private void switchToVirtualThread(VirtualThread vthread, boolean notifyJvmti) { + private void switchToVirtualThread(VirtualThread vthread) { Thread carrier = vthread.carrierThread; assert carrier == Thread.currentCarrierThread(); carrier.setCurrentThread(vthread); - if (notifyJvmti) { - notifyJvmtiHideFrames(false); - } + notifyJvmtiHideFrames(false); } /** @@ -419,17 +411,15 @@ final class VirtualThread extends BaseVirtualThread { */ @ChangesCurrentThread private boolean yieldContinuation() { - boolean notifyJvmti = notifyJvmtiEvents; - // unmount - if (notifyJvmti) notifyJvmtiUnmountBegin(false); + notifyJvmtiUnmount(true, false); unmount(); try { return Continuation.yield(VTHREAD_SCOPE); } finally { // re-mount mount(); - if (notifyJvmti) notifyJvmtiMountEnd(false); + notifyJvmtiMount(false, false); } } @@ -446,7 +436,7 @@ final class VirtualThread extends BaseVirtualThread { setState(PARKED); // notify JVMTI that unmount has completed, thread is parked - if (notifyJvmtiEvents) notifyJvmtiUnmountEnd(false); + notifyJvmtiUnmount(false, false); // may have been unparked while parking if (parkPermit && compareAndSetState(PARKED, RUNNABLE)) { @@ -462,7 +452,7 @@ final class VirtualThread extends BaseVirtualThread { setState(RUNNABLE); // notify JVMTI that unmount has completed, thread is runnable - if (notifyJvmtiEvents) notifyJvmtiUnmountEnd(false); + notifyJvmtiUnmount(false, false); // external submit if there are no tasks in the local task queue if (currentThread() instanceof CarrierThread ct && ct.getQueuedTaskCount() == 0) { @@ -483,7 +473,7 @@ final class VirtualThread extends BaseVirtualThread { assert (state() == TERMINATED) && (carrierThread == null); if (executed) { - if (notifyJvmtiEvents) notifyJvmtiUnmountEnd(true); + notifyJvmtiUnmount(false, true); } // notify anyone waiting for this virtual thread to terminate @@ -651,11 +641,11 @@ final class VirtualThread extends BaseVirtualThread { @ChangesCurrentThread private Future scheduleUnpark(Runnable unparker, long nanos) { // need to switch to current carrier thread to avoid nested parking - boolean notifyJvmti = switchToCarrierThread(); + switchToCarrierThread(); try { return UNPARKER.schedule(unparker, nanos, NANOSECONDS); } finally { - switchToVirtualThread(this, notifyJvmti); + switchToVirtualThread(this); } } @@ -666,11 +656,11 @@ final class VirtualThread extends BaseVirtualThread { private void cancel(Future future) { if (!future.isDone()) { // need to switch to current carrier thread to avoid nested parking - boolean notifyJvmti = switchToCarrierThread(); + switchToCarrierThread(); try { future.cancel(false); } finally { - switchToVirtualThread(this, notifyJvmti); + switchToVirtualThread(this); } } } @@ -690,11 +680,11 @@ final class VirtualThread extends BaseVirtualThread { int s = state(); if (s == PARKED && compareAndSetState(PARKED, RUNNABLE)) { if (currentThread instanceof VirtualThread vthread) { - boolean notifyJvmti = vthread.switchToCarrierThread(); + vthread.switchToCarrierThread(); try { submitRunContinuation(); } finally { - switchToVirtualThread(vthread, notifyJvmti); + switchToVirtualThread(vthread); } } else { submitRunContinuation(); @@ -1055,20 +1045,15 @@ final class VirtualThread extends BaseVirtualThread { // -- JVM TI support -- - private static volatile boolean notifyJvmtiEvents; // set by VM - + @IntrinsicCandidate @JvmtiMountTransition - private native void notifyJvmtiMountBegin(boolean firstMount); + private native void notifyJvmtiMount(boolean hide, boolean firstMount); + @IntrinsicCandidate @JvmtiMountTransition - private native void notifyJvmtiMountEnd(boolean firstMount); - - @JvmtiMountTransition - private native void notifyJvmtiUnmountBegin(boolean lastUnmount); - - @JvmtiMountTransition - private native void notifyJvmtiUnmountEnd(boolean lastUnmount); + private native void notifyJvmtiUnmount(boolean hide, boolean lastUnmount); + @IntrinsicCandidate @JvmtiMountTransition private native void notifyJvmtiHideFrames(boolean hide); diff --git a/src/java.base/share/native/libjava/VirtualThread.c b/src/java.base/share/native/libjava/VirtualThread.c index 1736a829da4..9e79486e356 100644 --- a/src/java.base/share/native/libjava/VirtualThread.c +++ b/src/java.base/share/native/libjava/VirtualThread.c @@ -32,11 +32,9 @@ #define VIRTUAL_THREAD "Ljava/lang/VirtualThread;" static JNINativeMethod methods[] = { - { "notifyJvmtiMountBegin", "(Z)V", (void *)&JVM_VirtualThreadMountBegin }, - { "notifyJvmtiMountEnd", "(Z)V", (void *)&JVM_VirtualThreadMountEnd }, - { "notifyJvmtiUnmountBegin", "(Z)V", (void *)&JVM_VirtualThreadUnmountBegin }, - { "notifyJvmtiUnmountEnd", "(Z)V", (void *)&JVM_VirtualThreadUnmountEnd }, - { "notifyJvmtiHideFrames", "(Z)V", (void *)&JVM_VirtualThreadHideFrames }, + { "notifyJvmtiMount", "(ZZ)V", (void *)&JVM_VirtualThreadMount }, + { "notifyJvmtiUnmount", "(ZZ)V", (void *)&JVM_VirtualThreadUnmount }, + { "notifyJvmtiHideFrames", "(Z)V", (void *)&JVM_VirtualThreadHideFrames }, }; JNIEXPORT void JNICALL