diff --git a/src/hotspot/share/services/management.cpp b/src/hotspot/share/services/management.cpp index bc70088d6ab..be6f1451e36 100644 --- a/src/hotspot/share/services/management.cpp +++ b/src/hotspot/share/services/management.cpp @@ -450,6 +450,16 @@ static void validate_thread_id_array(typeArrayHandle ids_ah, TRAPS) { } } +// Returns true if the JavaThread's Java object is a platform thread +static bool is_platform_thread(JavaThread* jt) { + if (jt != nullptr) { + oop thread_obj = jt->threadObj(); + return (thread_obj != nullptr) && !thread_obj->is_a(vmClasses::BoundVirtualThread_klass()); + } else { + return false; + } +} + #if INCLUDE_MANAGEMENT static void validate_thread_info_array(objArrayHandle infoArray_h, TRAPS) { @@ -462,6 +472,11 @@ static void validate_thread_info_array(objArrayHandle infoArray_h, TRAPS) { } } +// Returns true if the ThreadSnapshot's Java object is a platform thread +static bool is_platform_thread(ThreadSnapshot* ts) { + oop thread_obj = ts->threadObj(); + return (thread_obj != nullptr) && !thread_obj->is_a(vmClasses::BoundVirtualThread_klass()); +} static MemoryManager* get_memory_manager_from_jobject(jobject obj, TRAPS) { if (obj == nullptr) { @@ -1044,7 +1059,7 @@ static void do_thread_dump(ThreadDumpResult* dump_result, for (int i = 0; i < num_threads; i++) { jlong tid = ids_ah->long_at(i); JavaThread* jt = tlh.list()->find_JavaThread_from_java_tid(tid); - oop thread_obj = (jt != nullptr ? jt->threadObj() : (oop)nullptr); + oop thread_obj = is_platform_thread(jt) ? jt->threadObj() : (oop)nullptr; instanceHandle threadObj_h(THREAD, (instanceOop) thread_obj); thread_handle_array->append(threadObj_h); } @@ -1145,8 +1160,8 @@ JVM_ENTRY(jint, jmm_GetThreadInfo(JNIEnv *env, jlongArray ids, jint maxDepth, jo // For each thread, create an java/lang/management/ThreadInfo object // and fill with the thread information - if (ts->threadObj() == nullptr) { - // if the thread does not exist or now it is terminated, set threadinfo to null + if (!is_platform_thread(ts)) { + // if the thread does not exist, has terminated, or is a virtual thread, then set threadinfo to null infoArray_h->obj_at_put(index, nullptr); continue; } @@ -1211,8 +1226,8 @@ JVM_ENTRY(jobjectArray, jmm_DumpThreads(JNIEnv *env, jlongArray thread_ids, jboo int index = 0; for (ThreadSnapshot* ts = dump_result.snapshots(); ts != nullptr; ts = ts->next(), index++) { - if (ts->threadObj() == nullptr) { - // if the thread does not exist or now it is terminated, set threadinfo to null + if (!is_platform_thread(ts)) { + // if the thread does not exist, has terminated, or is a virtual thread, then set threadinfo to null result_h->obj_at_put(index, nullptr); continue; } @@ -1408,7 +1423,7 @@ JVM_ENTRY(jlong, jmm_GetThreadCpuTime(JNIEnv *env, jlong thread_id)) } else { ThreadsListHandle tlh; java_thread = tlh.list()->find_JavaThread_from_java_tid(thread_id); - if (java_thread != nullptr) { + if (is_platform_thread(java_thread)) { return os::thread_cpu_time((Thread*) java_thread); } } @@ -2101,8 +2116,7 @@ JVM_ENTRY(jlong, jmm_GetOneThreadAllocatedMemory(JNIEnv *env, jlong thread_id)) ThreadsListHandle tlh; JavaThread* java_thread = tlh.list()->find_JavaThread_from_java_tid(thread_id); - - if (java_thread != nullptr) { + if (is_platform_thread(java_thread)) { return java_thread->cooked_allocated_bytes(); } return -1; @@ -2141,7 +2155,7 @@ JVM_ENTRY(void, jmm_GetThreadAllocatedMemory(JNIEnv *env, jlongArray ids, ThreadsListHandle tlh; for (int i = 0; i < num_threads; i++) { JavaThread* java_thread = tlh.list()->find_JavaThread_from_java_tid(ids_ah->long_at(i)); - if (java_thread != nullptr) { + if (is_platform_thread(java_thread)) { sizeArray_h->long_at_put(i, java_thread->cooked_allocated_bytes()); } } @@ -2169,11 +2183,8 @@ JVM_ENTRY(jlong, jmm_GetThreadCpuTimeWithKind(JNIEnv *env, jlong thread_id, jboo } else { ThreadsListHandle tlh; java_thread = tlh.list()->find_JavaThread_from_java_tid(thread_id); - if (java_thread != nullptr) { - oop thread_obj = java_thread->threadObj(); - if (thread_obj != nullptr && !thread_obj->is_a(vmClasses::BaseVirtualThread_klass())) { - return os::thread_cpu_time((Thread*) java_thread, user_sys_cpu_time != 0); - } + if (is_platform_thread(java_thread)) { + return os::thread_cpu_time((Thread*) java_thread, user_sys_cpu_time != 0); } } return -1; @@ -2215,7 +2226,7 @@ JVM_ENTRY(void, jmm_GetThreadCpuTimesWithKind(JNIEnv *env, jlongArray ids, ThreadsListHandle tlh; for (int i = 0; i < num_threads; i++) { JavaThread* java_thread = tlh.list()->find_JavaThread_from_java_tid(ids_ah->long_at(i)); - if (java_thread != nullptr) { + if (is_platform_thread(java_thread)) { timeArray_h->long_at_put(i, os::thread_cpu_time((Thread*)java_thread, user_sys_cpu_time != 0)); } diff --git a/src/java.management/share/classes/java/lang/management/ThreadInfo.java b/src/java.management/share/classes/java/lang/management/ThreadInfo.java index 0038d2a1c29..9701e1ae0d8 100644 --- a/src/java.management/share/classes/java/lang/management/ThreadInfo.java +++ b/src/java.management/share/classes/java/lang/management/ThreadInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2023, 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 @@ -92,7 +92,6 @@ import sun.management.ThreadInfoCompositeData; */ public class ThreadInfo { - private boolean virtual; // accessed by ThreadImpl private String threadName; private long threadId; private long blockedTime; @@ -224,7 +223,6 @@ public class ThreadInfo { StackTraceElement[] stackTrace, MonitorInfo[] lockedMonitors, LockInfo[] lockedSynchronizers) { - this.virtual = t.isVirtual(); this.threadId = t.threadId(); this.threadName = t.getName(); this.threadState = ManagementFactoryHelper.toThreadState(state); diff --git a/src/java.management/share/classes/java/lang/management/ThreadMXBean.java b/src/java.management/share/classes/java/lang/management/ThreadMXBean.java index 0fdfb9573fc..52437adc6b3 100644 --- a/src/java.management/share/classes/java/lang/management/ThreadMXBean.java +++ b/src/java.management/share/classes/java/lang/management/ThreadMXBean.java @@ -65,17 +65,16 @@ import java.util.Map; * of thread IDs as the input parameter and return per-thread information. * *

Thread CPU time

- * A Java virtual machine implementation may support measuring - * the CPU time for the current thread, for any thread, or for no threads. + * A Java virtual machine implementation may support measuring the CPU time + * for the current platform thread, for any platform thread, or for no threads. * *

* The {@link #isThreadCpuTimeSupported} method can be used to determine * if a Java virtual machine supports measuring of the CPU time for any - * thread. The {@link #isCurrentThreadCpuTimeSupported} method can - * be used to determine if a Java virtual machine supports measuring of - * the CPU time for the current thread. - * A Java virtual machine implementation that supports CPU time measurement - * for any thread will also support that for the current thread. + * platform thread. The {@link #isCurrentThreadCpuTimeSupported()} method + * can be used to determine if a Java virtual machine supports measuring of + * the CPU time with the {@link #getCurrentThreadCpuTime()} and + * {@link #getCurrentThreadUserTime()} methods from a platform thread. * *

The CPU time provided by this interface has nanosecond precision * but not necessarily nanosecond accuracy. @@ -411,8 +410,9 @@ public interface ThreadMXBean extends PlatformManagedObject { * {@link #getThreadCpuTime getThreadCpuTime}(Thread.currentThread().threadId()); * * - * @return the total CPU time for the current thread if CPU time - * measurement is enabled; {@code -1} otherwise. + * @return the total CPU time for the current thread if the current + * thread is a platform thread and if CPU time measurement is enabled; + * {@code -1} otherwise. * * @throws UnsupportedOperationException if the Java * virtual machine does not support CPU time measurement for @@ -438,8 +438,9 @@ public interface ThreadMXBean extends PlatformManagedObject { * {@link #getThreadUserTime getThreadUserTime}(Thread.currentThread().threadId()); * * - * @return the user-level CPU time for the current thread if CPU time - * measurement is enabled; {@code -1} otherwise. + * @return the user-level CPU time for the current thread if the current + * thread is a platform thread and if CPU time measurement is enabled; + * {@code -1} otherwise. * * @throws UnsupportedOperationException if the Java * virtual machine does not support CPU time measurement for @@ -472,8 +473,8 @@ public interface ThreadMXBean extends PlatformManagedObject { * where CPU time measurement starts. * * @param id the thread ID of a thread - * @return the total CPU time for a thread of the specified ID - * if the thread of the specified ID exists, the thread is alive, + * @return the total CPU time for a thread of the specified ID if the + * thread of the specified ID is a platform thread, the thread is alive, * and CPU time measurement is enabled; * {@code -1} otherwise. * @@ -507,8 +508,8 @@ public interface ThreadMXBean extends PlatformManagedObject { * where CPU time measurement starts. * * @param id the thread ID of a thread - * @return the user-level CPU time for a thread of the specified ID - * if the thread of the specified ID exists, the thread is alive, + * @return the user-level CPU time for a thread of the specified ID if the + * thread of the specified ID is a platform thread, the thread is alive, * and CPU time measurement is enabled; * {@code -1} otherwise. * @@ -526,29 +527,31 @@ public interface ThreadMXBean extends PlatformManagedObject { /** * Tests if the Java virtual machine implementation supports CPU time - * measurement for any thread. + * measurement for any platform thread. * A Java virtual machine implementation that supports CPU time - * measurement for any thread will also support CPU time - * measurement for the current thread. + * measurement for any platform thread will also support CPU time + * measurement for the current thread, when the current thread is a + * platform thread. * * @return * {@code true} * if the Java virtual machine supports CPU time - * measurement for any thread; + * measurement for any platform thread; * {@code false} otherwise. */ public boolean isThreadCpuTimeSupported(); /** - * Tests if the Java virtual machine supports CPU time - * measurement for the current thread. + * Tests if the Java virtual machine supports CPU time measurement from + * a platform thread with the {@link #getCurrentThreadCpuTime()} and + * {@link #getCurrentThreadUserTime()} methods. * This method returns {@code true} if {@link #isThreadCpuTimeSupported} * returns {@code true}. * * @return * {@code true} - * if the Java virtual machine supports CPU time - * measurement for current thread; + * if the Java virtual machine supports CPU time measurement + * of the current platform thread; * {@code false} otherwise. */ public boolean isCurrentThreadCpuTimeSupported(); diff --git a/src/java.management/share/classes/sun/management/ThreadImpl.java b/src/java.management/share/classes/sun/management/ThreadImpl.java index 90cc8f3d1c4..cd34f4378a5 100644 --- a/src/java.management/share/classes/sun/management/ThreadImpl.java +++ b/src/java.management/share/classes/sun/management/ThreadImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2023, 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 @@ -30,6 +30,7 @@ import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.util.stream.Stream; import javax.management.ObjectName; +import java.util.Arrays; import java.util.Objects; /** @@ -130,7 +131,7 @@ public class ThreadImpl implements ThreadMXBean { public long[] getAllThreadIds() { Util.checkMonitorAccess(); Thread[] threads = getThreads(); - return platformThreadIds(threads); + return threadIds(threads); } @Override @@ -141,12 +142,7 @@ public class ThreadImpl implements ThreadMXBean { @Override public ThreadInfo getThreadInfo(long id, int maxDepth) { long[] ids = new long[] { id }; - ThreadInfo ti = getThreadInfo(ids, maxDepth)[0]; - if (ti == null || Util.isVirtual(ti)) { - return null; - } else { - return ti; - } + return getThreadInfo(ids, maxDepth)[0]; } @Override @@ -190,7 +186,6 @@ public class ThreadImpl implements ThreadMXBean { } else { getThreadInfo1(ids, maxDepth, infos); } - nullVirtualThreads(infos); return infos; } @@ -220,10 +215,6 @@ public class ThreadImpl implements ThreadMXBean { } private boolean verifyCurrentThreadCpuTime() { - // check if Thread CPU time measurement is supported. - if (Thread.currentThread().isVirtual()) { - throw new UnsupportedOperationException("Not supported by virtual threads"); - } if (!isCurrentThreadCpuTimeSupported()) { throw new UnsupportedOperationException( "Current thread CPU time measurement is not supported."); @@ -233,7 +224,7 @@ public class ThreadImpl implements ThreadMXBean { @Override public long getCurrentThreadCpuTime() { - if (verifyCurrentThreadCpuTime()) { + if (verifyCurrentThreadCpuTime() && !Thread.currentThread().isVirtual()) { return getThreadTotalCpuTime0(0); } return -1; @@ -283,11 +274,7 @@ public class ThreadImpl implements ThreadMXBean { long id = ids[0]; Thread thread = Thread.currentThread(); if (id == thread.threadId()) { - if (thread.isVirtual()) { - times[0] = -1; - } else { - times[0] = getThreadTotalCpuTime0(0); - } + times[0] = thread.isVirtual() ? -1L : getThreadTotalCpuTime0(0); } else { times[0] = getThreadTotalCpuTime0(id); } @@ -300,7 +287,7 @@ public class ThreadImpl implements ThreadMXBean { @Override public long getCurrentThreadUserTime() { - if (verifyCurrentThreadCpuTime()) { + if (verifyCurrentThreadCpuTime() && !Thread.currentThread().isVirtual()) { return getThreadUserCpuTime0(0); } return -1; @@ -326,11 +313,7 @@ public class ThreadImpl implements ThreadMXBean { long id = ids[0]; Thread thread = Thread.currentThread(); if (id == thread.threadId()) { - if (thread.isVirtual()) { - times[0] = -1; - } else { - times[0] = getThreadUserCpuTime0(0); - } + times[0] = thread.isVirtual() ? -1L : getThreadTotalCpuTime0(0); } else { times[0] = getThreadUserCpuTime0(id); } @@ -376,11 +359,7 @@ public class ThreadImpl implements ThreadMXBean { if (verified) { Thread thread = Thread.currentThread(); if (id == thread.threadId()) { - if (thread.isVirtual()) { - return -1L; - } else { - return getThreadAllocatedMemory0(0); - } + return thread.isVirtual() ? -1L : getThreadAllocatedMemory0(0); } else { return getThreadAllocatedMemory0(id); } @@ -431,19 +410,16 @@ public class ThreadImpl implements ThreadMXBean { * of threads is empty. */ private long[] threadsToIds(Thread[] threads) { - if (threads != null) { - long[] tids = platformThreadIds(threads); - if (tids.length > 0) { - return tids; - } + if (threads != null && threads.length > 0) { + return threadIds(threads); + } else { + return null; } - return null; } @Override public long[] findMonitorDeadlockedThreads() { Util.checkMonitorAccess(); - Thread[] threads = findMonitorDeadlockedThreads0(); return threadsToIds(threads); } @@ -496,12 +472,10 @@ public class ThreadImpl implements ThreadMXBean { public ThreadInfo[] getThreadInfo(long[] ids, boolean lockedMonitors, boolean lockedSynchronizers) { - ThreadInfo[] infos = dumpThreads0(ids, lockedMonitors, lockedSynchronizers, - Integer.MAX_VALUE); - nullVirtualThreads(infos); - return infos; + return dumpThreads0(ids, lockedMonitors, lockedSynchronizers, Integer.MAX_VALUE); } + @Override public ThreadInfo[] getThreadInfo(long[] ids, boolean lockedMonitors, boolean lockedSynchronizers, @@ -516,19 +490,16 @@ public class ThreadImpl implements ThreadMXBean { if (ids.length == 0) return new ThreadInfo[0]; verifyDumpThreads(lockedMonitors, lockedSynchronizers); - ThreadInfo[] infos = dumpThreads0(ids, lockedMonitors, lockedSynchronizers, maxDepth); - nullVirtualThreads(infos); - return infos; + return dumpThreads0(ids, lockedMonitors, lockedSynchronizers, maxDepth); } @Override public ThreadInfo[] dumpAllThreads(boolean lockedMonitors, boolean lockedSynchronizers) { - ThreadInfo[] infos = dumpAllThreads(lockedMonitors, lockedSynchronizers, - Integer.MAX_VALUE); - return platformThreads(infos); + return dumpAllThreads(lockedMonitors, lockedSynchronizers, Integer.MAX_VALUE); } + @Override public ThreadInfo[] dumpAllThreads(boolean lockedMonitors, boolean lockedSynchronizers, int maxDepth) { @@ -538,7 +509,9 @@ public class ThreadImpl implements ThreadMXBean { } verifyDumpThreads(lockedMonitors, lockedSynchronizers); ThreadInfo[] infos = dumpThreads0(null, lockedMonitors, lockedSynchronizers, maxDepth); - return platformThreads(infos); + return Arrays.stream(infos) + .filter(ti -> ti != null) + .toArray(ThreadInfo[]::new); } // VM support where maxDepth == -1 to request entire stack dump @@ -572,34 +545,11 @@ public class ThreadImpl implements ThreadMXBean { } /** - * Returns the thread identifiers of the platform threads in the given array. + * Returns the thread identifiers of the threads in the given array. */ - private static long[] platformThreadIds(Thread[] threads) { + private static long[] threadIds(Thread[] threads) { return Stream.of(threads) - .filter(t -> !t.isVirtual()) .mapToLong(Thread::threadId) .toArray(); } - - /** - * Returns the ThreadInfo objects from the given array that correspond to platform - * threads. - */ - private ThreadInfo[] platformThreads(ThreadInfo[] infos) { - return Stream.of(infos) - .filter(ti -> !Util.isVirtual(ti)) - .toArray(ThreadInfo[]::new); - } - - /** - * Set the elements of the given array to null if they correspond to a virtual thread. - */ - private static void nullVirtualThreads(ThreadInfo[] infos) { - for (int i = 0; i < infos.length; i++) { - ThreadInfo ti = infos[i]; - if (ti != null && Util.isVirtual(ti)) { - infos[i] = null; - } - } - } } diff --git a/src/java.management/share/classes/sun/management/Util.java b/src/java.management/share/classes/sun/management/Util.java index 80bad1e1523..e5a25792263 100644 --- a/src/java.management/share/classes/sun/management/Util.java +++ b/src/java.management/share/classes/sun/management/Util.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2023, 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 @@ -25,17 +25,11 @@ package sun.management; -import java.lang.reflect.Field; import java.lang.management.ManagementPermission; -import java.lang.management.ThreadInfo; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; import java.util.List; import javax.management.ObjectName; import javax.management.MalformedObjectNameException; - public class Util { private Util() {} // there are no instances of this class @@ -86,31 +80,4 @@ public class Util { public static void checkControlAccess() throws SecurityException { checkAccess(controlPermission); } - - /** - * Returns true if the given ThreadInfo is for a virtual thread. - */ - public static boolean isVirtual(ThreadInfo threadInfo) { - try { - return (boolean) THREADINFO_VIRTUAL.get(threadInfo); - } catch (Exception e) { - throw new InternalError(e); - } - } - - @SuppressWarnings("removal") - private static Field threadInfoVirtual() { - PrivilegedExceptionAction pa = () -> { - Field f = ThreadInfo.class.getDeclaredField("virtual"); - f.setAccessible(true); - return f; - }; - try { - return AccessController.doPrivileged(pa); - } catch (PrivilegedActionException e) { - throw new InternalError(e); - } - } - - private static final Field THREADINFO_VIRTUAL = threadInfoVirtual(); } diff --git a/src/jdk.management/share/classes/com/sun/management/ThreadMXBean.java b/src/jdk.management/share/classes/com/sun/management/ThreadMXBean.java index 0662bcc9366..9d2fae0e0be 100644 --- a/src/jdk.management/share/classes/com/sun/management/ThreadMXBean.java +++ b/src/jdk.management/share/classes/com/sun/management/ThreadMXBean.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2023, 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 @@ -53,9 +53,8 @@ public interface ThreadMXBean extends java.lang.management.ThreadMXBean { * @param ids an array of thread IDs. * @return an array of long values, each of which is the amount of CPU * time the thread whose ID is in the corresponding element of the input - * array of IDs has used, - * if the thread of a specified ID exists, the thread is alive, - * and CPU time measurement is enabled; + * array of IDs has used, if the thread of a specified ID is a platform + * thread, the thread is alive, and CPU time measurement is enabled; * {@code -1} otherwise. * * @throws NullPointerException if {@code ids} is {@code null} @@ -87,9 +86,8 @@ public interface ThreadMXBean extends java.lang.management.ThreadMXBean { * @param ids an array of thread IDs. * @return an array of long values, each of which is the amount of user * mode CPU time the thread whose ID is in the corresponding element of - * the input array of IDs has used, - * if the thread of a specified ID exists, the thread is alive, - * and CPU time measurement is enabled; + * the input array of IDs has used, if the thread of a specified ID is a + * platform thread, the thread is alive, and CPU time measurement is enabled; * {@code -1} otherwise. * * @throws NullPointerException if {@code ids} is {@code null} @@ -161,10 +159,9 @@ public interface ThreadMXBean extends java.lang.management.ThreadMXBean { * * @param id the thread ID of a thread * @return an approximation of the total memory allocated, in bytes, in - * heap memory for the thread with the specified ID - * if the thread with the specified ID exists, the thread is alive, - * and thread memory allocation measurement is enabled; - * {@code -1} otherwise. + * heap memory for the thread with the specified ID if the thread with the + * specified ID is a platform thread, the thread is alive, and thread memory + * allocation measurement is enabled; {@code -1} otherwise. * * @throws IllegalArgumentException if {@code id} {@code <=} {@code 0}. * @throws UnsupportedOperationException if the Java virtual diff --git a/test/jdk/com/sun/management/ThreadMXBean/VirtualThreads.java b/test/jdk/com/sun/management/ThreadMXBean/VirtualThreads.java new file mode 100644 index 00000000000..d07309c48b2 --- /dev/null +++ b/test/jdk/com/sun/management/ThreadMXBean/VirtualThreads.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2023, 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. + * + * 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. + */ + +/** + * @test id=default + * @bug 8284161 8303242 + * @summary Test com.sun.management.ThreadMXBean with virtual threads + * @enablePreview + * @library /test/lib + * @run junit/othervm VirtualThreads + */ + +/** + * @test id=no-vmcontinuations + * @requires vm.continuations + * @enablePreview + * @library /test/lib + * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations VirtualThreads + */ + +import java.lang.management.ManagementFactory; +import java.util.concurrent.locks.LockSupport; +import com.sun.management.ThreadMXBean; + +import jdk.test.lib.thread.VThreadRunner; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.*; + +public class VirtualThreads { + + /** + * Test that ThreadMXBean::getCurrentThreadAllocatedBytes() returns -1 when + * invoked from a virtual thread. + */ + @Test + void testGetCurrentThreadAllocatedBytes() throws Exception { + ThreadMXBean bean = ManagementFactory.getPlatformMXBean(ThreadMXBean.class); + assumeTrue(bean.isThreadAllocatedMemorySupported(), + "Thread memory allocation measurement not supported"); + + VThreadRunner.run(() -> { + assertEquals(-1L, bean.getCurrentThreadAllocatedBytes()); + }); + } + + /** + * Test that ThreadMXBean.getThreadAllocatedBytes(long) returns -1 when invoked + * with the thread ID of a virtual thread. + */ + @Test + void testGetThreadAllocatedBytes1() { + ThreadMXBean bean = ManagementFactory.getPlatformMXBean(ThreadMXBean.class); + assumeTrue(bean.isThreadAllocatedMemorySupported(), + "Thread memory allocation measurement not supported"); + + Thread vthread = Thread.startVirtualThread(LockSupport::park); + try { + long allocated = bean.getThreadAllocatedBytes(vthread.threadId()); + assertEquals(-1L, allocated); + } finally { + LockSupport.unpark(vthread); + } + } + + /** + * Test that ThreadMXBean.getThreadAllocatedBytes(long) returns -1 when invoked + * by a virtual thread with its own thread id. + */ + @Test + void testGetThreadAllocatedBytes2() throws Exception { + ThreadMXBean bean = ManagementFactory.getPlatformMXBean(ThreadMXBean.class); + assumeTrue(bean.isThreadAllocatedMemorySupported(), + "Thread memory allocation measurement not supported"); + + VThreadRunner.run(() -> { + long tid = Thread.currentThread().threadId(); + long allocated = bean.getThreadAllocatedBytes(tid); + assertEquals(-1L, allocated); + }); + } + + /** + * Test that ThreadMXBean.getThreadAllocatedBytes(long[]) returns -1 for + * elements that correspond to a virtual thread. + */ + @Test + void testGetThreadAllocatedBytes3() { + ThreadMXBean bean = ManagementFactory.getPlatformMXBean(ThreadMXBean.class); + assumeTrue(bean.isThreadAllocatedMemorySupported(), + "Thread memory allocation measurement not supported"); + + Thread vthread = Thread.startVirtualThread(LockSupport::park); + try { + long tid0 = Thread.currentThread().threadId(); + long tid1 = vthread.threadId(); + long[] tids = new long[] { tid0, tid1 }; + long[] allocated = bean.getThreadAllocatedBytes(tids); + assertTrue(allocated[0] >= 0L); + assertEquals(-1L, allocated[1]); + } finally { + LockSupport.unpark(vthread); + } + } + + /** + * Test that ThreadMXBean.getThreadCpuTime(long[]) returns -1 for + * elements that correspond to a virtual thread. + */ + @Test + void testGetThreadCpuTime() { + ThreadMXBean bean = ManagementFactory.getPlatformMXBean(ThreadMXBean.class); + assumeTrue(bean.isThreadCpuTimeSupported(), "Thread CPU time measurement not supported"); + + Thread vthread = Thread.startVirtualThread(LockSupport::park); + try { + long tid0 = Thread.currentThread().threadId(); + long tid1 = vthread.threadId(); + long[] tids = new long[] { tid0, tid1 }; + long[] cpuTimes = bean.getThreadCpuTime(tids); + assertTrue(cpuTimes[0] >= 0L); + assertEquals(-1L, cpuTimes[1]); + } finally { + LockSupport.unpark(vthread); + } + } + + /** + * Test that ThreadMXBean.getThreadUserTime(long[])returns -1 for + * elements that correspond to a virtual thread. + */ + @Test + void testGetThreadUserTime() { + ThreadMXBean bean = ManagementFactory.getPlatformMXBean(ThreadMXBean.class); + assumeTrue(bean.isThreadCpuTimeSupported(), "Thread CPU time measurement not supported"); + + Thread vthread = Thread.startVirtualThread(LockSupport::park); + try { + long tid0 = Thread.currentThread().threadId(); + long tid1 = vthread.threadId(); + long[] tids = new long[] { tid0, tid1 }; + long[] userTimes = bean.getThreadUserTime(tids); + assertTrue(userTimes[0] >= 0L); + assertEquals(-1L, userTimes[1]); + } finally { + LockSupport.unpark(vthread); + } + } +} diff --git a/test/jdk/java/lang/management/ThreadMXBean/VirtualThreads.java b/test/jdk/java/lang/management/ThreadMXBean/VirtualThreads.java index e9bd48732d4..a64a4c06f7f 100644 --- a/test/jdk/java/lang/management/ThreadMXBean/VirtualThreads.java +++ b/test/jdk/java/lang/management/ThreadMXBean/VirtualThreads.java @@ -23,10 +23,11 @@ /** * @test id=default - * @bug 8284161 8290562 + * @bug 8284161 8290562 8303242 * @summary Test java.lang.management.ThreadMXBean with virtual threads * @enablePreview * @modules java.base/java.lang:+open java.management + * @library /test/lib * @run junit/othervm VirtualThreads */ @@ -35,6 +36,7 @@ * @requires vm.continuations * @enablePreview * @modules java.base/java.lang:+open java.management + * @library /test/lib * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations VirtualThreads */ @@ -45,24 +47,52 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.nio.channels.Selector; import java.util.Arrays; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; +import java.util.stream.Collectors; +import jdk.test.lib.thread.VThreadRunner; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assumptions.*; public class VirtualThreads { /** - * Test that ThreadMXBean::getAllThreadsIds does not include thread ids for - * virtual threads. + * Test that ThreadMXBean.dumpAllThreads does not include virtual threads. + */ + @ParameterizedTest + @ValueSource(ints = {0, Integer.MAX_VALUE}) + void testDumpAllThreads(int maxDepth) { + Thread vthread = Thread.startVirtualThread(LockSupport::park); + try { + ThreadMXBean bean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] infos = bean.dumpAllThreads(false, false, maxDepth); + Set tids = Arrays.stream(infos) + .map(ThreadInfo::getThreadId) + .collect(Collectors.toSet()); + + // current thread should be included + assertTrue(tids.contains(Thread.currentThread().threadId())); + + // virtual thread should not be included + assertFalse(tids.contains(vthread.threadId())); + } finally { + LockSupport.unpark(vthread); + } + } + + /** + * Test that ThreadMXBean::getAllThreadsIds does not include virtual threads. */ @Test - void testGetAllThreadIds() throws Exception { + void testGetAllThreadIds() { Thread vthread = Thread.startVirtualThread(LockSupport::park); try { long[] tids = ManagementFactory.getThreadMXBean().getAllThreadIds(); @@ -80,39 +110,83 @@ public class VirtualThreads { } /** - * Test that ThreadMXBean.getThreadInfo(long) returns null for a virtual thread. + * Test that ThreadMXBean.getThreadInfo(long, maxDepth) returns null for a virtual + * thread. */ - @Test - void testGetThreadInfo1() throws Exception { + @ParameterizedTest + @ValueSource(ints = {0, Integer.MAX_VALUE}) + void testGetThreadInfo1(int maxDepth) { Thread vthread = Thread.startVirtualThread(LockSupport::park); try { long tid = vthread.threadId(); - ThreadInfo info = ManagementFactory.getThreadMXBean().getThreadInfo(tid); - assertTrue(info == null); + ThreadInfo info = ManagementFactory.getThreadMXBean().getThreadInfo(tid, maxDepth); + assertNull(info); } finally { LockSupport.unpark(vthread); } } /** - * Test that ThreadMXBean.getThreadInfo(long) returns null when invoked by a virtual - * thread with its own thread id. + * Test that ThreadMXBean.getThreadInfo(long, maxDepth) returns null when invoked + * by a virtual thread with its own thread id. */ - @Test - void testGetThreadInfo2() throws Exception { - runInVirtualThread(() -> { + @ParameterizedTest + @ValueSource(ints = {0, Integer.MAX_VALUE}) + void testGetThreadInfo2(int maxDepth) throws Exception { + VThreadRunner.run(() -> { long tid = Thread.currentThread().threadId(); - ThreadInfo info = ManagementFactory.getThreadMXBean().getThreadInfo(tid); - assertTrue(info == null); + ThreadInfo info = ManagementFactory.getThreadMXBean().getThreadInfo(tid, maxDepth); + assertNull(info); }); } + /** + * Test that ThreadMXBean.getThreadInfo(long[], maxDepth) returns a null ThreadInfo + * for elements that correspond to a virtual thread. + */ + @ParameterizedTest + @ValueSource(ints = {0, Integer.MAX_VALUE}) + void testGetThreadInfo3(int maxDepth) { + Thread vthread = Thread.startVirtualThread(LockSupport::park); + try { + long tid0 = Thread.currentThread().threadId(); + long tid1 = vthread.threadId(); + long[] tids = new long[] { tid0, tid1 }; + ThreadInfo[] infos = ManagementFactory.getThreadMXBean().getThreadInfo(tids, maxDepth); + assertEquals(tid0, infos[0].getThreadId()); + assertNull(infos[1]); + } finally { + LockSupport.unpark(vthread); + } + } + + /** + * Test that ThreadMXBean.getThreadInfo(long[], boolean, boolean, maxDepth) returns + * a null ThreadInfo for elements that correspond to a virtual thread. + */ + @ParameterizedTest + @ValueSource(ints = {0, Integer.MAX_VALUE}) + void testGetThreadInfo4(int maxDepth) { + Thread vthread = Thread.startVirtualThread(LockSupport::park); + try { + long tid0 = Thread.currentThread().threadId(); + long tid1 = vthread.threadId(); + long[] tids = new long[] { tid0, tid1 }; + ThreadMXBean bean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] infos = bean.getThreadInfo(tids, false, false, maxDepth); + assertEquals(tid0, infos[0].getThreadId()); + assertNull(infos[1]); + } finally { + LockSupport.unpark(vthread); + } + } + /** * Test ThreadMXBean.getThreadInfo(long) with the thread id of a carrier thread. * The stack frames of the virtual thread should not be returned. */ @Test - void testGetThreadInfo3() throws Exception { + void testGetThreadInfoCarrierThread() throws Exception { assumeTrue(supportsCustomScheduler(), "No support for custom schedulers"); try (ExecutorService pool = Executors.newFixedThreadPool(1)) { var carrierRef = new AtomicReference(); @@ -146,40 +220,24 @@ public class VirtualThreads { assertFalse(contains(stack, "java.nio.channels.Selector")); // carrier should not be holding any monitors - assertTrue(info.getLockedMonitors().length == 0); + assertEquals(0, info.getLockedMonitors().length); } } } - /** - * Test that ThreadMXBean.getThreadInfo(long[]) returns a null ThreadInfo for - * elements that correspond to a virtual thread. - */ - @Test - void testGetThreadInfo4() throws Exception { - Thread vthread = Thread.startVirtualThread(LockSupport::park); - try { - long tid0 = Thread.currentThread().threadId(); - long tid1 = vthread.threadId(); - long[] tids = new long[] { tid0, tid1 }; - ThreadInfo[] infos = ManagementFactory.getThreadMXBean().getThreadInfo(tids); - assertTrue(infos[0].getThreadId() == tid0); - assertTrue(infos[1] == null); - } finally { - LockSupport.unpark(vthread); - } - } - /** * Test that ThreadMXBean.getThreadCpuTime(long) returns -1 for a virtual thread. */ @Test void testGetThreadCpuTime1() { + ThreadMXBean bean = ManagementFactory.getThreadMXBean(); + assumeTrue(bean.isThreadCpuTimeSupported(), "Thread CPU time measurement not supported"); + Thread vthread = Thread.startVirtualThread(LockSupport::park); try { long tid = vthread.threadId(); - long cpuTime = ManagementFactory.getThreadMXBean().getThreadCpuTime(tid); - assertTrue(cpuTime == -1L); + long cpuTime = bean.getThreadCpuTime(tid); + assertEquals(-1L, cpuTime); } finally { LockSupport.unpark(vthread); } @@ -191,10 +249,13 @@ public class VirtualThreads { */ @Test void testGetThreadCpuTime2() throws Exception { - runInVirtualThread(() -> { + ThreadMXBean bean = ManagementFactory.getThreadMXBean(); + assumeTrue(bean.isThreadCpuTimeSupported(), "Thread CPU time measurement not supported"); + + VThreadRunner.run(() -> { long tid = Thread.currentThread().threadId(); - long cpuTime = ManagementFactory.getThreadMXBean().getThreadCpuTime(tid); - assertTrue(cpuTime == -1L); + long cpuTime = bean.getThreadCpuTime(tid); + assertEquals(-1L, cpuTime); }); } @@ -203,11 +264,14 @@ public class VirtualThreads { */ @Test void testGetThreadUserTime1() { + ThreadMXBean bean = ManagementFactory.getThreadMXBean(); + assumeTrue(bean.isThreadCpuTimeSupported(), "Thread CPU time measurement not supported"); + Thread vthread = Thread.startVirtualThread(LockSupport::park); try { long tid = vthread.threadId(); long userTime = ManagementFactory.getThreadMXBean().getThreadUserTime(tid); - assertTrue(userTime == -1L); + assertEquals(-1L, userTime); } finally { LockSupport.unpark(vthread); } @@ -219,73 +283,61 @@ public class VirtualThreads { */ @Test void testGetThreadUserTime2() throws Exception { - runInVirtualThread(() -> { + ThreadMXBean bean = ManagementFactory.getThreadMXBean(); + assumeTrue(bean.isThreadCpuTimeSupported(), "Thread CPU time measurement not supported"); + + VThreadRunner.run(() -> { long tid = Thread.currentThread().threadId(); long userTime = ManagementFactory.getThreadMXBean().getThreadUserTime(tid); - assertTrue(userTime == -1L); + assertEquals(-1L, userTime); }); } /** - * Test that ThreadMXBean::getCurrentThreadCpuTime throws UOE when invoked - * on a virtual thread. + * Test that ThreadMXBean::isCurrentThreadCpuTimeSupported returns true when + * CPU time measurement for the current thread is supported. + */ + @Test + void testIsCurrentThreadCpuTimeSupported() throws Exception { + ThreadMXBean bean = ManagementFactory.getThreadMXBean(); + assumeTrue(bean.isCurrentThreadCpuTimeSupported(), + "Thread CPU time measurement for the current thread not supported"); + + VThreadRunner.run(() -> { + assertTrue(bean.isCurrentThreadCpuTimeSupported()); + }); + } + + /** + * Test that ThreadMXBean::getCurrentThreadCpuTime returns -1 when invoked + * from a virtual thread. */ @Test void testGetCurrentThreadCpuTime() throws Exception { - runInVirtualThread(() -> { - assertThrows(UnsupportedOperationException.class, - () -> ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime()); + ThreadMXBean bean = ManagementFactory.getThreadMXBean(); + assumeTrue(bean.isCurrentThreadCpuTimeSupported(), + "Thread CPU time measurement for the current thread not supported"); + + VThreadRunner.run(() -> { + assertEquals(-1L, bean.getCurrentThreadCpuTime()); }); } /** - * Test that ThreadMXBean::getCurrentThreadUserTime throws UOE when - * invoked on a virtual thread. + * Test that ThreadMXBean::getCurrentThreadUserTime returns -1 when invoked + * from a virtual thread. */ @Test void testGetCurrentThreadUserTime() throws Exception { - runInVirtualThread(() -> { - assertThrows(UnsupportedOperationException.class, - () -> ManagementFactory.getThreadMXBean().getCurrentThreadUserTime()); + ThreadMXBean bean = ManagementFactory.getThreadMXBean(); + assumeTrue(bean.isCurrentThreadCpuTimeSupported(), + "Thread CPU time measurement for the current thread not supported"); + + VThreadRunner.run(() -> { + assertEquals(-1L, bean.getCurrentThreadUserTime()); }); } - /** - * Test that ThreadMXBean::getCurrentThreadAllocatedBytes returns -1 when - * invoked on a virtual thread. - */ - @Test - void testGetCurrentThreadAllocatedBytes() throws Exception { - runInVirtualThread(() -> { - long allocated = ManagementFactory.getPlatformMXBean(com.sun.management.ThreadMXBean.class) - .getCurrentThreadAllocatedBytes(); - assertTrue(allocated == -1L); - }); - } - - interface ThrowingRunnable { - void run() throws Exception; - } - - private static void runInVirtualThread(ThrowingRunnable task) throws Exception { - AtomicReference exc = new AtomicReference<>(); - Runnable target = () -> { - try { - task.run(); - } catch (Error e) { - exc.set(new RuntimeException(e)); - } catch (Exception e) { - exc.set(e); - } - }; - Thread thread = Thread.ofVirtual().start(target); - thread.join(); - Exception e = exc.get(); - if (e != null) { - throw e; - } - } - private static boolean contains(StackTraceElement[] stack, String className) { return Arrays.stream(stack) .map(StackTraceElement::getClassName)