8328083: degrade virtual thread support for GetObjectMonitorUsage

Reviewed-by: cjplummer, alanb
This commit is contained in:
Serguei Spitsyn 2024-05-23 12:07:17 +00:00
parent 4e6d851f3f
commit b890336e11
12 changed files with 186 additions and 57 deletions

View File

@ -8249,37 +8249,42 @@ class C2 extends C1 implements I2 {
<field id="owner">
<jthread/>
<description>
The thread owning this monitor, or <code>nullptr</code> if unused
The platform thread owning this monitor, or <code>nullptr</code> if owned
by a virtual thread or not owned
</description>
</field>
<field id="entry_count">
<jint/>
<description>
The number of times the owning thread has entered the monitor
The number of times the platform thread owning this monitor has entered it,
or <code>0</code> if owned by a virtual thread or not owned
</description>
</field>
<field id="waiter_count">
<jint/>
<description>
The number of threads waiting to own this monitor
The number of platform threads waiting to own this monitor, or <code>0</code>
if only virtual threads are waiting or no threads are waiting
</description>
</field>
<field id="waiters">
<allocfieldbuf><jthread/></allocfieldbuf>
<description>
The <code>waiter_count</code> waiting threads
The <code>waiter_count</code> waiting platform threads
</description>
</field>
<field id="notify_waiter_count">
<jint/>
<description>
The number of threads waiting to be notified by this monitor
The number of platform threads waiting to own this monitor, or <code>0</code>
if only virtual threads are waiting to be notified or no threads are waiting
to be notified
</description>
</field>
<field id="notify_waiters">
<allocfieldbuf><jthread/></allocfieldbuf>
<description>
The <code>notify_waiter_count</code> threads waiting to be notified
The <code>notify_waiter_count</code> platform threads waiting to be notified
</description>
</field>
</typedef>
@ -8287,6 +8292,12 @@ class C2 extends C1 implements I2 {
Get information about the object's monitor.
The fields of the <functionlink id="jvmtiMonitorUsage"></functionlink> structure
are filled in with information about usage of the monitor.
<p/>
<b> This function does not support getting information about an object's monitor
when it is owned by a virtual thread. It also does not support returning a
reference to virtual threads that are waiting to own a monitor or waiting to
be notified.
</b>
<todo>
Decide and then clarify suspend requirements.
</todo>

View File

@ -1482,14 +1482,19 @@ JvmtiEnvBase::get_object_monitor_usage(JavaThread* calling_thread, jobject objec
// first derive the object's owner and entry_count (if any)
owning_thread = ObjectSynchronizer::get_lock_owner(tlh.list(), hobj);
if (owning_thread != nullptr) {
Handle th(current_thread, get_vthread_or_thread_oop(owning_thread));
oop thread_oop = get_vthread_or_thread_oop(owning_thread);
bool is_virtual = java_lang_VirtualThread::is_instance(thread_oop);
if (is_virtual) {
thread_oop = nullptr;
}
Handle th(current_thread, thread_oop);
ret.owner = (jthread)jni_reference(calling_thread, th);
// The recursions field of a monitor does not reflect recursions
// as lightweight locks before inflating the monitor are not included.
// We have to count the number of recursive monitor entries the hard way.
// We pass a handle to survive any GCs along the way.
ret.entry_count = count_locked_objects(owning_thread, hobj);
ret.entry_count = is_virtual ? 0 : count_locked_objects(owning_thread, hobj);
}
// implied else: entry_count == 0
@ -1513,6 +1518,7 @@ JvmtiEnvBase::get_object_monitor_usage(JavaThread* calling_thread, jobject objec
// this object has a lightweight monitor
}
jint skipped = 0;
if (mon != nullptr) {
// Robustness: the actual waiting list can be smaller.
// The nWait count we got from the mon->waiters() may include the re-entering
@ -1522,11 +1528,16 @@ JvmtiEnvBase::get_object_monitor_usage(JavaThread* calling_thread, jobject objec
for (ObjectWaiter* waiter = mon->first_waiter();
waiter != nullptr && (nWait == 0 || waiter != mon->first_waiter());
waiter = mon->next_waiter(waiter)) {
JavaThread *w = mon->thread_of_waiter(waiter);
oop thread_oop = get_vthread_or_thread_oop(w);
if (java_lang_VirtualThread::is_instance(thread_oop)) {
skipped++;
}
nWait++;
}
}
ret.waiter_count = nWant;
ret.notify_waiter_count = nWait;
ret.notify_waiter_count = nWait - skipped;
// Allocate memory for heavyweight and lightweight monitor.
jvmtiError err;
@ -1561,13 +1572,20 @@ JvmtiEnvBase::get_object_monitor_usage(JavaThread* calling_thread, jobject objec
}
if (ret.notify_waiter_count > 0) { // we have threads waiting to be notified in Object.wait()
ObjectWaiter *waiter = mon->first_waiter();
jint skipped = 0;
for (int i = 0; i < nWait; i++) {
JavaThread *w = mon->thread_of_waiter(waiter);
oop thread_oop = get_vthread_or_thread_oop(w);
bool is_virtual = java_lang_VirtualThread::is_instance(thread_oop);
assert(w != nullptr, "sanity check");
// If the thread was found on the ObjectWaiter list, then
// it has not been notified.
Handle th(current_thread, get_vthread_or_thread_oop(w));
ret.notify_waiters[i] = (jthread)jni_reference(calling_thread, th);
if (java_lang_VirtualThread::is_instance(thread_oop)) {
skipped++;
} else {
// If the thread was found on the ObjectWaiter list, then
// it has not been notified.
Handle th(current_thread, get_vthread_or_thread_oop(w));
ret.notify_waiters[i - skipped] = (jthread)jni_reference(calling_thread, th);
}
waiter = mon->next_waiter(waiter);
}
}

View File

@ -1183,7 +1183,8 @@ void Threads::metadata_handles_do(void f(Metadata*)) {
}
#if INCLUDE_JVMTI
// Get count of Java threads that are waiting to enter or re-enter the specified monitor.
// Get Java threads that are waiting to enter or re-enter the specified monitor.
// Java threads that are executing mounted virtual threads are not included.
GrowableArray<JavaThread*>* Threads::get_pending_threads(ThreadsList * t_list,
int count,
address monitor) {
@ -1194,14 +1195,16 @@ GrowableArray<JavaThread*>* Threads::get_pending_threads(ThreadsList * t_list,
for (JavaThread* p : *t_list) {
if (!p->can_call_java()) continue;
oop thread_oop = JvmtiEnvBase::get_vthread_or_thread_oop(p);
if (java_lang_VirtualThread::is_instance(thread_oop)) {
continue;
}
// The first stage of async deflation does not affect any field
// used by this comparison so the ObjectMonitor* is usable here.
address pending = (address)p->current_pending_monitor();
address waiting = (address)p->current_waiting_monitor();
oop thread_oop = JvmtiEnvBase::get_vthread_or_thread_oop(p);
bool is_virtual = java_lang_VirtualThread::is_instance(thread_oop);
jint state = is_virtual ? JvmtiEnvBase::get_vthread_state(thread_oop, p)
: JvmtiEnvBase::get_thread_state(thread_oop, p);
// do not include virtual threads to the list
jint state = JvmtiEnvBase::get_thread_state(thread_oop, p);
if (pending == monitor || (waiting == monitor &&
(state & JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER))
) { // found a match

View File

@ -129,7 +129,8 @@ public:
// Print threads busy compiling, and returns the number of printed threads.
static unsigned print_threads_compiling(outputStream* st, char* buf, int buflen, bool short_form = false);
// Get count of Java threads that are waiting to enter or re-enter the specified monitor.
// Get Java threads that are waiting to enter or re-enter the specified monitor.
// Java threads that are executing mounted virtual threads are not included.
static GrowableArray<JavaThread*>* get_pending_threads(ThreadsList * t_list,
int count, address monitor);

View File

@ -1617,11 +1617,14 @@ JDWP "Java(tm) Debug Wire Protocol"
(object object "The object ID")
)
(Reply
(threadObject owner "The monitor owner, or null if it is not currently owned.")
(int entryCount "The number of times the monitor has been entered.")
(Repeat waiters "The total number of threads that are waiting to enter or re-enter "
"the monitor, or waiting to be notified by the monitor."
(threadObject thread "A thread waiting for this monitor.")
(threadObject owner "The platform thread owning this monitor, or null "
"if owned by a virtual thread or not owned.")
(int entryCount "The number of times the owning platform thread has entered the monitor, "
"or 0 if owned by a virtual thread or not owned.")
(Repeat waiters "The total number of platform threads that are waiting to enter or re-enter "
"the monitor, or waiting to be notified by the monitor, or 0 if "
"only virtual threads are waiting or no threads are waiting."
(threadObject thread "A platform thread waiting for this monitor.")
)
)
(ErrorSet
@ -2871,7 +2874,7 @@ JDWP "Java(tm) Debug Wire Protocol"
"if not explicitly requested."
(int requestID
"Request that generated event (or 0 if this "
"Request that generated event, or 0 if this "
"event is automatically generated.")
(threadObject thread "Initial thread")
)

View File

@ -345,7 +345,7 @@ public interface ObjectReference extends Value {
/**
* Returns a List containing a {@link ThreadReference} for
* each thread currently waiting for this object's monitor.
* each platform thread currently waiting for this object's monitor.
* See {@link ThreadReference#currentContendedMonitor} for
* information about when a thread is considered to be waiting
* for a monitor.
@ -355,7 +355,8 @@ public interface ObjectReference extends Value {
* operation is supported.
*
* @return a List of {@link ThreadReference} objects. The list
* has zero length if no threads are waiting for the monitor.
* has zero length if no threads are waiting for the monitor,
* or only virtual threads are waiting for the monitor.
* @throws java.lang.UnsupportedOperationException if the
* target VM does not support this operation.
* @throws IncompatibleThreadStateException if any
@ -366,7 +367,7 @@ public interface ObjectReference extends Value {
throws IncompatibleThreadStateException;
/**
* Returns an {@link ThreadReference} for the thread, if any,
* Returns a {@link ThreadReference} for the platform thread, if any,
* which currently owns this object's monitor.
* See {@link ThreadReference#ownedMonitors} for a definition
* of ownership.
@ -375,8 +376,9 @@ public interface ObjectReference extends Value {
* {@link VirtualMachine#canGetMonitorInfo} to determine if the
* operation is supported.
*
* @return the {@link ThreadReference} which currently owns the
* monitor, or null if it is unowned.
* @return the {@link ThreadReference} of the platform thread which
* currently owns the monitor, or null if the monitor is owned
* by a virtual thread or not owned.
*
* @throws java.lang.UnsupportedOperationException if the
* target VM does not support this operation.
@ -386,8 +388,9 @@ public interface ObjectReference extends Value {
ThreadReference owningThread() throws IncompatibleThreadStateException;
/**
* Returns the number times this object's monitor has been
* entered by the current owning thread.
* Returns the number of times this object's monitor has been entered by
* the current owning thread if the owning thread is platform thread;
* Returns 0 if not owned by a platform thread.
* See {@link ThreadReference#ownedMonitors} for a definition
* of ownership.
* <p>

View File

@ -114,6 +114,13 @@ public class ObjectMonitorUsage {
throw new Error("Unexpected " + e);
}
}
static Thread expOwnerThread() {
return Thread.currentThread().isVirtual() ? null : Thread.currentThread();
}
static int expEntryCount() {
return Thread.currentThread().isVirtual() ? 0 : 1;
}
/* Scenario #0:
* - owning: 0
@ -127,14 +134,18 @@ public class ObjectMonitorUsage {
setTestedMonitor(lockCheck);
Thread[] wThreads = startWaitingThreads(isVirtual);
final int expWaitingCount = isVirtual ? 0 : NUMBER_OF_WAITING_THREADS;
// The numbers below describe the testing scenario, not the expected results.
// The expected numbers are different for virtual threads because
// they are not supported by JVMTI GetObjectMonitorUsage.
// entry count: 0
// count of threads waiting to enter: 0
// count of threads waiting to re-enter: 0
// count of threads waiting to be notified: NUMBER_OF_WAITING_THREADS
check(lockCheck, null, 0, // no owner thread
0, // count of threads waiting to enter: 0
NUMBER_OF_WAITING_THREADS);
expWaitingCount);
synchronized (lockCheck) {
lockCheck.notifyAll();
@ -158,20 +169,30 @@ public class ObjectMonitorUsage {
Thread[] eThreads = null;
synchronized (lockCheck) {
// Virtual threads are not supported by GetObjectMonitorUsage.
// Correct the expected values for the virtual thread case.
int expEnteringCount = isVirtual ? 0 : NUMBER_OF_ENTERING_THREADS;
// The numbers below describe the testing scenario, not the expected results.
// The expected numbers are different for virtual threads because
// they are not supported by JVMTI GetObjectMonitorUsage.
// entry count: 1
// count of threads waiting to enter: 0
// count of threads waiting to re-enter: 0
// count of threads waiting to be notified: 0
check(lockCheck, Thread.currentThread(), 1, 0, 0);
check(lockCheck, expOwnerThread(), expEntryCount(), 0, 0);
eThreads = startEnteringThreads(isVirtual);
// The numbers below describe the testing scenario, not the expected results.
// The expected numbers are different for virtual threads because
// they are not supported by JVMTI GetObjectMonitorUsage.
// entry count: 1
// count of threads waiting to enter: NUMBER_OF_ENTERING_THREADS
// count of threads waiting to re-enter: 0
// count of threads waiting to be notified: 0
check(lockCheck, Thread.currentThread(), 1,
NUMBER_OF_ENTERING_THREADS,
check(lockCheck, expOwnerThread(), expEntryCount(),
expEnteringCount,
0 /* count of threads waiting to be notified: 0 */);
}
@ -195,15 +216,23 @@ public class ObjectMonitorUsage {
Thread[] eThreads = null;
synchronized (lockCheck) {
// Virtual threads are not supported by the GetObjectMonitorUsage.
// Correct the expected values for the virtual thread case.
int expEnteringCount = isVirtual ? 0 : NUMBER_OF_ENTERING_THREADS;
int expWaitingCount = isVirtual ? 0 : NUMBER_OF_WAITING_THREADS;
eThreads = startEnteringThreads(isVirtual);
// The numbers below describe the testing scenario, not the expected results.
// The expected numbers are different for virtual threads because
// they are not supported by JVMTI GetObjectMonitorUsage.
// entry count: 1
// count of threads waiting to enter: NUMBER_OF_ENTERING_THREADS
// count of threads waiting to re-enter: 0
// count of threads waiting to be notified: NUMBER_OF_WAITING_THREADS
check(lockCheck, Thread.currentThread(), 1,
NUMBER_OF_ENTERING_THREADS,
NUMBER_OF_WAITING_THREADS);
check(lockCheck, expOwnerThread(), expEntryCount(),
expEnteringCount,
expWaitingCount);
lockCheck.notifyAll();
}
@ -234,35 +263,51 @@ public class ObjectMonitorUsage {
Thread[] eThreads = null;
synchronized (lockCheck) {
// Virtual threads are not supported by GetObjectMonitorUsage.
// Correct the expected values for the virtual thread case.
int expEnteringCount = isVirtual ? 0 : NUMBER_OF_ENTERING_THREADS;
int expWaitingCount = isVirtual ? 0 : NUMBER_OF_WAITING_THREADS;
// The numbers below describe the testing scenario, not the expected results.
// The expected numbers are different for virtual threads because
// they are not supported by JVMTI GetObjectMonitorUsage.
// entry count: 1
// count of threads waiting to enter: 0
// count of threads waiting to re-enter: 0
// count of threads waiting to be notified: NUMBER_OF_WAITING_THREADS
check(lockCheck, Thread.currentThread(), 1,
check(lockCheck, expOwnerThread(), expEntryCount(),
0, // number of threads waiting to enter or re-enter
NUMBER_OF_WAITING_THREADS);
expWaitingCount);
eThreads = startEnteringThreads(isVirtual);
// The numbers below describe the testing scenario, not the expected results.
// The expected numbers are different for virtual threads because
// they are not supported by JVMTI GetObjectMonitorUsage.
// entry count: 1
// count of threads waiting to enter: NUMBER_OF_ENTERING_THREADS
// count of threads waiting to re-enter: 0
// count of threads waiting to be notified: NUMBER_OF_WAITING_THREADS
check(lockCheck, Thread.currentThread(), 1,
NUMBER_OF_ENTERING_THREADS,
NUMBER_OF_WAITING_THREADS);
check(lockCheck, expOwnerThread(), expEntryCount(),
expEnteringCount,
expWaitingCount);
for (int i = 0; i < NUMBER_OF_WAITING_THREADS; i++) {
expEnteringCount = isVirtual ? 0 : NUMBER_OF_ENTERING_THREADS + i + 1;
expWaitingCount = isVirtual ? 0 : NUMBER_OF_WAITING_THREADS - i - 1;
lockCheck.notify(); // notify waiting threads one by one
// now the notified WaitingTask has to be blocked on the lockCheck re-enter
// The numbers below describe the testing scenario, not the expected results.
// The expected numbers are different for virtual threads because
// they are not supported by JVMTI GetObjectMonitorUsage.
// entry count: 1
// count of threads waiting to enter: NUMBER_OF_ENTERING_THREADS
// count of threads waiting to re-enter: i + 1
// count of threads waiting to be notified: NUMBER_OF_WAITING_THREADS - i - 1
check(lockCheck, Thread.currentThread(), 1,
NUMBER_OF_ENTERING_THREADS + i + 1,
NUMBER_OF_WAITING_THREADS - i - 1);
check(lockCheck, expOwnerThread(), expEntryCount(),
expEnteringCount,
expWaitingCount);
}
}
joinThreads(wThreads);

View File

@ -197,8 +197,11 @@ public class entrycount002 {
display("Checking entryCount for iteration : " + i);
try {
// The lockRef.entryCount() is expected to return 0 if the owner thread is virtual.
int expEntryCount = mainThread.isVirtual() ? 0 : i;
int entryCount = lockRef.entryCount();
if (entryCount != i) {
if (entryCount != expEntryCount) {
exitCode = Consts.TEST_FAILED;
complain("entry count method returned unexpected value : " + entryCount +
"\n\t expected one : " + i);

View File

@ -200,6 +200,15 @@ public class owningthread002 {
try {
ThreadReference thread = lockRef.owningThread();
// The lockRef.owningThread() is expected to return null if tested threads are virtual.
if (eventThread.isVirtual()) {
if (thread == null) {
display("expected null is returned` by owningThread method on virtual thread: " + eventThread.name());
} else {
complain("owningThread returned ThreadReference of virtual thread instead of null: " + thread.name());
}
continue;
}
if (thread.name().indexOf(owningthread002a.threadNamePrefix) < 0) {
exitCode = Consts.TEST_FAILED;
complain("owningThread returned ThreadReference with unexpected name: " + thread.name());

View File

@ -122,6 +122,9 @@ public class waitingthreads002 {
if (thread.name().indexOf(waitingthreads002a.threadNamePrefix) >= 0 &&
thread.status() == ThreadReference.THREAD_STATUS_MONITOR ) {
waitingCount++;
// Virtual threads are not present in result returned by objRef.waitingThreads().
if (!thread.isVirtual()) {
}
}
}
}
@ -159,7 +162,9 @@ public class waitingthreads002 {
objRef = (ObjectReference) debuggeeClass.getValue(debuggeeClass.fieldByName(fieldName));
try {
List waitingThreads = objRef.waitingThreads();
if (waitingThreads.size() != waitingthreads002a.threadCount) {
final boolean vthreadMode = "Virtual".equals(System.getProperty("test.thread.factory"));
final int expWaitingCount = vthreadMode ? 0 : waitingthreads002a.threadCount;
if (waitingThreads.size() != expWaitingCount) {
exitStatus = Consts.TEST_FAILED;
complain("waitingThreads method returned list with unexpected size for " + fieldName +
"\n\t expected value : " + waitingthreads002a.threadCount + "; got one : " + waitingThreads.size());

View File

@ -61,8 +61,16 @@ public class objmonusage001 {
syncObject[i] = new Object();
runn[i] = new objmonusage001a(mainThread, i, syncObject[i]);
}
// Virtual threads are not supported by GetObjectMonitorUsage.
// Correct the expected values if the test is executed with
// JTREG_TEST_THREAD_FACTORY=Virtual.
Thread expOwner = mainThread.isVirtual() ? null : mainThread;
int expEntryCount = mainThread.isVirtual() ? 0 : 1;
for (int i = 0; i < NUMBER_OF_THREADS; i++) {
Thread expNotifyWaiter = runn[i].isVirtual() ? null : runn[i];
int expNotifyWaitingCount = runn[i].isVirtual() ? 0 : 1;
synchronized (syncObject[i]) {
runn[i].start();
try {
@ -92,8 +100,8 @@ public class objmonusage001 {
// This is a stable verification point because the worker thread is in wait()
// and is not notified and the main thread is doing the verification.
//
check(NUMBER_OF_THREADS + i, syncObject[i], mainThread, 1,
null, 0, runn[i], 1);
check(NUMBER_OF_THREADS + i, syncObject[i], expOwner, expEntryCount,
null, 0, expNotifyWaiter, expNotifyWaitingCount);
}
// Check #3:
@ -117,7 +125,7 @@ public class objmonusage001 {
// and is not notified and the main thread is doing the verification.
//
check((NUMBER_OF_THREADS * 2) + i, syncObject[i], null, 0,
null, 0, runn[i], 1);
null, 0, expNotifyWaiter, expNotifyWaitingCount);
}
for (int i = 0; i < NUMBER_OF_THREADS; i++) {
@ -147,6 +155,14 @@ class objmonusage001a extends Thread {
}
public void run() {
// Virtual threads are not supported by GetObjectMonitorUsage.
// Correct the expected values if the test is executed with
// JTREG_TEST_THREAD_FACTORY=Virtual.
Thread expOwner = this.isVirtual() ? null : this;
Thread expNotifyWaiter = mainThread.isVirtual() ? null : mainThread;
int expEntryCount = this.isVirtual() ? 0 : 1;
int expNotifyWaitingCount = mainThread.isVirtual() ? 0 : 1;
synchronized (syncObject) {
// Check #1:
// - owner == this_thread:
@ -166,8 +182,8 @@ class objmonusage001a extends Thread {
// This is a stable verification point because the main thread is in wait()
// and is not notified and this worker thread is doing the verification.
//
objmonusage001.check(index, syncObject, this, 1,
null, 0, mainThread, 1);
objmonusage001.check(index, syncObject, expOwner, expEntryCount,
null, 0, expNotifyWaiter, expNotifyWaitingCount);
syncObject.notify();
try {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2024, 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
@ -60,11 +60,23 @@ public class objmonusage004 {
Thread currThread = Thread.currentThread();
ContendThread thr[] = new ContendThread[NUMBER_OF_THREADS];
synchronized (lockCheck) {
// Virtual threads are not supported by GetObjectMonitorUsage.
// Correct the expected values if the test is executed with
// JTREG_TEST_THREAD_FACTORY=Virtual.
Thread expOwner = currThread.isVirtual() ? null : currThread;
int expEntryCount = currThread.isVirtual() ? 0 : 2;
synchronized (lockCheck) {
check(lockCheck, currThread, 2, 0);
check(lockCheck, expOwner, expEntryCount, 0);
}
expEntryCount = currThread.isVirtual() ? 0 : 1;
int expWaiterCount = 0;
for (int i = 0; i < NUMBER_OF_THREADS; i++) {
thr[i] = new ContendThread();
if (!thr[i].isVirtual()) {
expWaiterCount++;
}
synchronized (lockStart) {
thr[i].start();
try {
@ -74,7 +86,7 @@ public class objmonusage004 {
throw new Error("Unexpected " + e);
}
}
check(lockCheck, currThread, 1, i + 1);
check(lockCheck, expOwner, expEntryCount, expWaiterCount);
}
}