openj9
openj9 copied to clipboard
Loom: JVMTI support
Depends on https://github.com/eclipse-openj9/openj9/issues/15177
JVMTI
- can_support_virtual_threads //new capbility
- Suspend/ResumeAllVirtualThreads //new APIs
- finds all vthreads and suspends or resumes them
- StopThread
- disallowerd on vthreads
- GetThreadInfo
- vthread is always daemon
- vthread priotity is always normal
- RunAgentThread
- cannot be vthread
- GetThreadGroupChildren
- does not include vthreads
- GetAllStackTraces
- does not include vthreads
- PopFrame/ForceEarlyReturnXXX/SetLocalXXX
- allowed to fail if vthread
- intial approach, do not allow for unmounted vthreads
- if mounted, should just work, although should behave as pinned
- cant modify pin state here, since effectively threadlocal
- GetCurrentThreadCpuTime/GetThreadCpuTime
- disallowed on vthreads
- JVMTI_THREAD_STATE_PARKED
- includes sleep state for vthreads
See https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html
Revised List
Potentially, the following JVMTI methods are impacted by Loom:
SetThreadLocalStorage(assigned to @EricYangIBM; covered by the TLS work): https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#SetThreadLocalStorageGetThreadLocalStorage(assigned to @EricYangIBM; covered by the TLS work): https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetThreadLocalStorageSetEventCallbacks(assigned to @EricYangIBM; covered by the TLS work): https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#SetEventCallbacksSetEventNotificationMode(assigned to @EricYangIBM; covered by the TLS work): https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#SetEventNotificationMode- For JVMTI event callbacks, each
jvmtiHook*function invokesprepareForEvent, which accessesJ9JVMTIThreadData->threadEventEnable(assigned to @EricYangIBM; covered by the TLS work): https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#jvmtiEventCallbacks. GetThreadState: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetThreadState- Implementation details: See https://github.com/eclipse-openj9/openj9/issues/15183#issuecomment-1167767823.
- ~
GetCurrentThread: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetCurrentThread~- No change needed. See https://github.com/eclipse-openj9/openj9/issues/15183#issuecomment-1167767823.
SuspendThread: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#SuspendThreadSuspendThreadList: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#SuspendThreadListSuspendAllVirtualThreads: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#SuspendAllVirtualThreadsResumeThread: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#ResumeThreadResumeThreadList: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#ResumeThreadListResumeAllVirtualThreads: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#ResumeAllVirtualThreads- ~
StopThread: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#StopThread~- Completed. See https://github.com/eclipse-openj9/openj9/pull/15418.
InterruptThread: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#InterruptThread- Phase 1: https://github.com/eclipse-openj9/openj9/pull/15690.
- Phase 2: Ignore the interrupt code for
(NULL == targetThread)i.e. a yielded virtual thread, and avoid pinning a yielded virtual thread injvmtiHelpers.c::getVMThreadto evade theomthread_monitor_*costs.
GetThreadInfo: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetThreadInfo- Implementation details: See https://github.com/eclipse-openj9/openj9/issues/15183#issuecomment-1187580033.
GetOwnedMonitorInfo: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetOwnedMonitorInfo- Phase 1: https://github.com/eclipse-openj9/openj9/pull/15690.
- Phase 2: https://github.com/eclipse-openj9/openj9/issues/15183#issuecomment-1219243366.
GetOwnedMonitorStackDepthInfo: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetOwnedMonitorStackDepthInfo- Phase 1: https://github.com/eclipse-openj9/openj9/pull/15690.
- Phase 2: https://github.com/eclipse-openj9/openj9/issues/15183#issuecomment-1219243366.
GetCurrentContendedMonitor: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetCurrentContendedMonitor- Phase 1: https://github.com/eclipse-openj9/openj9/pull/15690.
- Phase 2: https://github.com/eclipse-openj9/openj9/issues/15183#issuecomment-1219243366.
- ~
GetThreadGroupChildren: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetThreadGroupChildren.~- Completed. See https://github.com/eclipse-openj9/openj9/pull/15539.
GetStackTrace: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetStackTrace- Phase 1: https://github.com/eclipse-openj9/openj9/pull/15690.
- Phase 2: https://github.com/eclipse-openj9/openj9/issues/15183#issuecomment-1219243366.
GetThreadListStackTraces: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetThreadListStackTraces- Phase 1: https://github.com/eclipse-openj9/openj9/pull/15690.
- Phase 2: https://github.com/eclipse-openj9/openj9/issues/15183#issuecomment-1219243366.
GetFrameCount(assigned to @thallium): https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetFrameCount- Phase 1: https://github.com/eclipse-openj9/openj9/pull/15690.
- Phase 2: https://github.com/eclipse-openj9/openj9/issues/15183#issuecomment-1219243366.
- ~
PopFrame: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#PopFrame~- Completed: https://github.com/eclipse-openj9/openj9/pull/15710.
GetFrameLocation: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetFrameLocation- Phase 1: https://github.com/eclipse-openj9/openj9/pull/15690.
- Phase 2: https://github.com/eclipse-openj9/openj9/issues/15183#issuecomment-1219243366.
NotifyFramePop: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#NotifyFramePop- Phase 1: https://github.com/eclipse-openj9/openj9/pull/15690.
- Phase 2: https://github.com/eclipse-openj9/openj9/issues/15183#issuecomment-1219243366.
GetLocal(Object|Instance|Int|Long|Float|Double): https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetLocalObject- Phase 1: https://github.com/eclipse-openj9/openj9/pull/15690.
- Phase 2: https://github.com/eclipse-openj9/openj9/issues/15183#issuecomment-1219243366.
SetLocal(Object|Int|Long|Float|Double): https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#SetLocalObject- Phase 1: https://github.com/eclipse-openj9/openj9/pull/15690.
- Phase 2: https://github.com/eclipse-openj9/openj9/issues/15183#issuecomment-1219243366.
- ~
ForceEarlyReturn(Object|Int|Long|Float|Double|Void): https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#ForceEarlyReturnObject~- Completed: https://github.com/eclipse-openj9/openj9/pull/15710.
- ~
GetCurrentThreadCpuTime: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetCurrentThreadCpuTime~- Completed. See https://github.com/eclipse-openj9/openj9/pull/15418.
- ~
GetThreadCpuTime: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetThreadCpuTime~- Completed. See https://github.com/eclipse-openj9/openj9/pull/15418.
- ~
RunAgentThread: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#RunAgentThread~- Completed. See https://github.com/eclipse-openj9/openj9/pull/15418.
- ~
GetAllThreads: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetAllThreads~- Completed: https://github.com/eclipse-openj9/openj9/pull/15635.
- ~
can_support_virtual_threads: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#jvmtiCapabilities.can_support_virtual_threads~- Completed. See https://github.com/eclipse-openj9/openj9/pull/15254 and https://github.com/eclipse-openj9/openj9/pull/15451.
GetAllStackTraces: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetAllStackTraces- Phase 1: https://github.com/eclipse-openj9/openj9/pull/15690.
- Phase 2: https://github.com/eclipse-openj9/openj9/issues/15183#issuecomment-1219287248.
Task 1: Handle error cases
re https://github.com/eclipse-openj9/openj9/issues/15183#issuecomment-1160712504
Examples:
29. GetCurrentThreadCpuTime: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetCurrentThreadCpuTime
30. GetThreadCpuTime: https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#GetThreadCpuTime
Return JVMTI_ERROR_UNSUPPORTED_OPERATION for virtual threads
vmconstantpool.xml:
<classref name="java/lang/VirtualThread"/>
bool isVirtualThread(J9VMThread *vmThread, jthread jthrd) {
bool result = false;
if (isSameOrSuperClassOf(
J9VMJAVALANGVIRTUALTHREAD_OR_NULL(vmThread->javaVM),
J9OBJECT_CLAZZ(vmThread, *(j9object_t *)jthrd)
) {
result = true;
}
return result;
}
@tajila Will J9VMThread->threadObject always be up-to-date for Loom?
jvmtiGetCurrentThreadCpuTime(jvmtiEnv *env, jlong *nanos_ptr) {
...
J9VMThread * currentThread = NULL;
j9object_t threadObject = NULL;
jvmtiError rc = JVMTI_ERROR_NONE;
rc = getCurrentVMThread(vm, ¤tThread);
if (JVMTI_ERROR_NONE != rc) {
return rc;
}
threadObject = currentThread->threadObject;
if (isSameOrSuperClassOf(
J9VMJAVALANGVIRTUALTHREAD_OR_NULL(currentThread->javaVM),
J9OBJECT_CLAZZ(currentThread, threadObject)
) {
return JVMTI_ERROR_UNSUPPORTED_OPERATION;
}
...
}
@tajila Will J9VMThread->threadObject always be up-to-date for Loom?
yes
Is there a priority for functions to update or should I just go down the list in the spec?
Is there a priority for functions to update or should I just go down the list in the spec?
Some functions will require the changes for https://github.com/eclipse-openj9/openj9/issues/15177. You can support the easiest ones first, and the ones not dependent on https://github.com/eclipse-openj9/openj9/issues/15177.
Looking at the first three thread functions, do they need to be updated?
- GetThreadState Are there any changes needed for getVMThreadStateHelper()? Thread flags used differently for virtual threads?
- GetCurrentState
Depends on
vm->internalVMFunctions->currentVMThread, I don't think it needs any other changes. https://github.com/eclipse-openj9/openj9/blob/master/runtime/jvmti/jvmtiThread.c#L1096 - GetAllThreads Iterates over J9VMThread.linkNext, does this include virtual threads? https://github.com/eclipse-openj9/openj9/blob/master/runtime/jvmti/jvmtiThread.c#L163
re https://github.com/eclipse-openj9/openj9/issues/15183#issuecomment-1167668040:
GetThreadState: There are some differences between Thread.state and VirtualThread.state. Example: A virtual thread that is sleeping, inThread.sleep, may haveJVMTI_THREAD_STATE_PARKEDset instead ofJVMTI_THREAD_STATE_SLEEPING.JVMTI_THREAD_STATE_RUNNABLEmeans that a thread is runnable. A virtual thread has two similar states:RUNNABLEandRUNNING. Also, a virtual thread has aPINNEDstate.GetCurrentThread:((J9VMThread *)(vm->internalVMFunctions->currentVMThread()))->threadObjectshould provide the correct Java programming language thread. No change needed for this method.GetAllThreads:J9VMThread.linkNextshould only include platform and carrier threads. It should not have virtual threads. The JVMTI doc tells us to ignore virtual threads for this method. So, no change is needed for this method.
We can also use the JVMTI tests in the extensions repo to verify our functionality. These tests have been updated for Project Loom.
GetThreadStatetests: https://github.com/ibmruntimes/openj9-openjdk-jdk19/tree/openj9/test/hotspot/jtreg/serviceability/jvmti/thread/GetThreadState
Other Project Loom related JVMTI tests: https://github.com/ibmruntimes/openj9-openjdk-jdk19/tree/openj9/test/hotspot/jtreg/serviceability/jvmti/thread
How will we implement Get/SetThreadLocalStorage for virtual threads? The tls for platform threads is attached to omr thread.
How will we implement Get/SetThreadLocalStorage for virtual threads? The tls for platform threads is attached to omr thread.
It can be moved as a hidden field into java.lang.Thread (class). In the Loom world, this will allow us to associate a pointer value with each environment-thread pair.
Old:
struct J9Thread {
void* tls[J9THREAD_MAX_TLS_KEYS];
New:
Add hidden field into java.lang.Thread to store the above TLS array. It can be dynamically initialized.
Old:
#define THREAD_DATA_FOR_VMTHREAD(j9env, vmThread) \
((J9JVMTIThreadData *) omrthread_tls_get((vmThread)->osThread, (j9env)->tlsKey))
New:
Get TLS from java.lang.Thread's hidden field
(J9JVMTIThreadData *)READU(TLS[j9env->tlsKey - 1]);
We will also need to account for the initialization and destruction of the native structures associated to this hidden field.
@tajila @gacholio Can you provide feedback on the above approach?
At a glance, no, this is not correct. Our TLS is effectively useless in the presence of vthreads, so we will simply have to stop using it in many places.
Correct answer here is probably to add a hidden field to Thread to contain the JVMTI data itself, not some fake TLS implementation.
Correct answer here is probably to add a hidden field to Thread to contain the JVMTI data itself, not some fake TLS implementation.
Will JVMTI data be a struct as follows?
struct JVMTIThreadData {
List of {J9JVMTIEnv, J9JVMTIThreadData} pairs;
Other thread specific JVMTI details if needed;
}
To avoid searching a list, we could essentially reimplement TLS - store a fixed-size array in the Thread hidden field, and manage the keys ourselves. This will mean freeing the TLS when the Thread is collected (which may be a pain).
Currently the TLS data struct gets allocated for each existing os thread when a jvmti env is allocated (allocateEnvironment in jvmtiHelpers.c) and new threads when a J9VMThread is created. TLS gets deallocated upon jvmti env deallocation or J9VMThread death. (J9HOOK_VM_THREAD_CREATED and J9HOOK_VM_THREAD_DESTROY)
Could we change these hooks to when a Thread is started/destroyed? Or maybe use the J9HOOK_VM_THREAD_STARTED/J9HOOK_VM_THREAD_END events instead?
Also I don't think the jvmtiEventThreadStart ThreadStart; and jvmtiEventThreadEnd ThreadEnd; callback functions exist/are used (same with the virtual thread versions), could they be used?
Also note the TLS only has to exist while the thread is alive (JVMTI_ERROR_THREAD_NOT_ALIVE)
J9HOOK_VM_THREAD_CREATED is special in that it's fired under lock (it does not report any JVMTI events) and is allowed to fail (e.g. malloc fail for TLS).
(J9HOOK_VM_THREAD_CREATED and J9HOOK_VM_THREAD_DESTROY) Could we change these hooks to when a Thread is started/destroyed?
We will need new hooks for j.l.Thread creation/destruction. Reusing VM_THREAD hooks will be misleading since multiple j.l.Threads can be associated to a single VM_THREAD.
Also I don't think the jvmtiEventThreadStart ThreadStart; and jvmtiEventThreadEnd ThreadEnd; callback functions exist/are used (same with the virtual thread versions), could they be used?
Callbacks are defined by the user. Example ThreadStart use-case: https://github.com/eclipse-openj9/openj9/blob/24b14b4743f82a648712bdca71a5b870828e65f9/runtime/tests/jvmtitests/src/com/ibm/jvmti/tests/eventThreadStart/ets001.c#L46
@gacholio @tajila Can we init and destroy j.l.Thread specific structures in jvmtiHookThreadStarted and jvmtiHookThreadDestroy before the callback is invoked? With this approach, can we avoid new J9 hooks for j.l.Thread creation/destruction?
Doesnt that have the same problem as J9HOOK_VM_THREAD_CREATED since those hooks apply to vmthreads which may be mapped to multiple virtual threads?
Doesnt that have the same problem as J9HOOK_VM_THREAD_CREATED since those hooks apply to vmthreads which may be mapped to multiple virtual threads?
There are also the newly added jvmtiEventVirtualThreadStart VirtualThreadStart; and jvmtiEventVirtualThreadEnd VirtualThreadEnd; for which we have jvmtiHookVirtualThreadStarted and jvmtiHookVirtualThreadEnd
Doesnt that have the same problem as J9HOOK_VM_THREAD_CREATED since those hooks apply to vmthreads which may be mapped to multiple virtual threads?
These events should work for Java threads (not VM threads): https://download.java.net/java/early_access/jdk19/docs/specs/jvmti.html#ThreadStart. But they may just be restricted to the threads launched by RunAgentThread.
Approach 1
Finalization is deprecated and expensive for GC.
Thread object {
Constructor {
Allocate data-struct to store JVMTI thread-local storage
Allocation can also be delayed to first use.
}
GC collect/destroy {
// Is there a way to coordinate with the GC to free this data-struct during collection?
Free data-struct if allocated
}
}
Approach 2
Requires more footprint. But, less overhead on the GC.
JVM startup {
Allocate a global struct. E.g. HashTable<key=threadObj, value=Data>.
Allocation can also be delayed to first use.
}
SetThreadLocalStorage {
// Entries will be unused if thread objects are collected by the GC
HashTable.put(threadObj, ...)
}
GetThreadLocalStorage {
HashTable.get(threadObj, ...)
}
JVM destroy {
Free the global struct
}
How are vthreads created? Do you call start on them like normal threads (presumably overridden)?
But they may just be restricted to the threads launched by RunAgentThread.
I don't see why this is the case, especially since the thread object associated with the agent thread cannot be a virtual thread and VirtualThreadStartEvents seem to be generated: https://github.com/ibmruntimes/openj9-openjdk-jdk/blob/openj9/src/java.base/share/classes/java/lang/VirtualThread.java#L280 Why can't we allocate the array upon this event?
The thread start even currently cannot fail, so allocating here would mean either changing the internal APIs or asserting instead of failing (a bad idea). Please answer my previous question - if a native is run to start vthreads, we can fire the CREATED event there.
Just had a discussion with @EricYangIBM I believe the following natives are all we need. These are in j.l.VirtualThread
@JvmtiMountTransition
private native void notifyJvmtiMountBegin(boolean firstMount);
@JvmtiMountTransition
private native void notifyJvmtiMountEnd(boolean firstMount);
@JvmtiMountTransition
private native void notifyJvmtiUnmountBegin(boolean lastUnmount);
@JvmtiMountTransition
private native void notifyJvmtiUnmountEnd(boolean lastUnmount);
notifyJvmtiMountBegin(true) is called when the vthread is started. notifyJvmtiUnmountEnd(true) is called when the vthread ends.
We can use these natives to implement jvmtiEventVirtualThreadStart/End hooks.
Additionally, we can also use these natives to keep track of which vthreads are currently live.
As for the TLS question, I think Approach 2 makes more sense to me. We can associated a hashtable which each jvmtiEnv where the key is the threadobj and the value is the tls data
Do you have enough control to guarantee you can call a native when a vthread ends (don't these threads "end" when they are collected by the GC?)?
Also, hash tables containing objects will be problematic.
Do you have enough control to guarantee you can call a native when a vthread ends (don't these threads "end" when they are collected by the GC?)?
The natives are effectively the first and last things that the vthreads do when they are live. The vthread can't run more code once notifyJvmtiUnmountEnd(true) is called, the threadObject may still exist but its thread state will be TERMINATED
Also, hash tables containing objects will be problematic.
True, we will need some other kind of ID
Summary:
- Add pointer hidden field in java/lang/Thread to store pointers to J9JVMTIThreadData
- Keys will be managed similarly to
omrthread_tls_functions, one per jvmtiEnv - ~~For each platform thread when jvmti env is created, allocate 124 size array (if not allocated already from another env) and J9JVMTIThreadData at array[key]~~
Allocate at
SetThreadLocalStorageorSetEventNotificationModefor a given thread under a lock - ~~For each live virtual thread when jvmti env is created, also allocate 124 size array and J9JVMTIThreadData~~
- When a platform thread is created (
J9HOOK_VM_THREAD_CREATED) allocate array and J9JVMTIThreadData - ~~When a virtual thread is created (new J9Hook similar to above) also allocate array and J9JVMTIThreadData~~
- When a jvmtiEnv is destroyed loop through all platform ~~and virtual~~ threads and free the key associated with the env
- Deallocate J9JVMTIThreadData and hidden field array when ~~platform (
J9HOOK_VM_THREAD_DESTROY) and~~ virtual threads die - ~~The
notifyJvmtiMountBegin(true)andnotifyJvmtiUnmountEnd(true)natives will update the list of live virtual threads and also call the new hooks~~
Please correct if wrong
Edit - Updated to reflect lazy init approach
When a jvmtiEnv is destroyed loop through all platform and virtual threads and free the key associated with the env
Is the key not a field of jvmtiEnv? Is looping through threads required?
Keys will be managed similarly to omrthread_tls_ functions, one per jvmtiEnv
TLS: Can you describe the new functions which will be implemented on the J9 side? Which OMR functions will be reused?
Is looping through threads required?
Won't we need to free the tls data for each thread's tls[env->key] since the thread destroy hooks will be unregistered? Also I think we will need a global array for each env to keep track of used keys. Currently omr does lib->tls_finalizers[key - 1] = NULL; to free a key, we will need something similar to this array. (Also I don't think it currently frees the tls data in omrthread_tls_free, it only sets it to null)
I think we will have to reimplement all of the omr functions since omr uses tls keys for non-jvmti tls as well