diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index 2521eb01493..858faf22a31 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -65,6 +65,7 @@ import java.util.PropertyPermission; import java.util.ResourceBundle; import java.util.Set; import java.util.WeakHashMap; +import java.util.concurrent.Executor; import java.util.function.Supplier; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; @@ -2766,6 +2767,10 @@ public final class System { } } + public Executor virtualThreadDefaultScheduler() { + return VirtualThread.defaultScheduler(); + } + public StackWalker newStackWalkerInstance(Set options, ContinuationScope contScope, Continuation continuation) { diff --git a/src/java.base/share/classes/java/lang/VirtualThread.java b/src/java.base/share/classes/java/lang/VirtualThread.java index da05e77f12b..6eb20fa9e2e 100644 --- a/src/java.base/share/classes/java/lang/VirtualThread.java +++ b/src/java.base/share/classes/java/lang/VirtualThread.java @@ -142,6 +142,14 @@ final class VirtualThread extends BaseVirtualThread { // termination object when joining, created lazily if needed private volatile CountDownLatch termination; + + /** + * Returns the default scheduler. + */ + static Executor defaultScheduler() { + return DEFAULT_SCHEDULER; + } + /** * Returns the continuation scope used for virtual threads. */ diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java index dbe116599e0..8a53e1532eb 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -44,6 +44,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import java.util.stream.Stream; @@ -601,6 +602,11 @@ public interface JavaLangAccess { */ void unparkVirtualThread(Thread thread); + /** + * Returns the virtual thread default scheduler. + */ + Executor virtualThreadDefaultScheduler(); + /** * Creates a new StackWalker */ diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index 74a7451582c..4e43245e520 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -172,6 +172,7 @@ module java.base { jdk.jartool, jdk.jlink, jdk.jfr, + jdk.management, jdk.net, jdk.sctp, jdk.crypto.cryptoki; diff --git a/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java b/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java index cdc5998426d..721f03e7de1 100644 --- a/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java +++ b/src/jdk.management/share/classes/com/sun/management/internal/PlatformMBeanProviderImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -41,6 +41,7 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import javax.management.DynamicMBean; +import jdk.management.VirtualThreadSchedulerMXBean; import sun.management.ManagementFactoryHelper; import sun.management.spi.PlatformMBeanProvider; @@ -163,6 +164,41 @@ public final class PlatformMBeanProviderImpl extends PlatformMBeanProvider { } }); + /** + * VirtualThreadSchedulerMXBean. + */ + initMBeanList.add(new PlatformComponent() { + private final Set> mbeanInterfaces = + Set.of(VirtualThreadSchedulerMXBean.class); + private final Set mbeanInterfaceNames = + Set.of(VirtualThreadSchedulerMXBean.class.getName()); + private VirtualThreadSchedulerMXBean impl; + + @Override + public Set> mbeanInterfaces() { + return mbeanInterfaces; + } + + @Override + public Set mbeanInterfaceNames() { + return mbeanInterfaceNames; + } + + @Override + public String getObjectNamePattern() { + return "jdk.management:type=VirtualThreadScheduler"; + } + + @Override + public Map nameToMBeanMap() { + VirtualThreadSchedulerMXBean impl = this.impl; + if (impl == null) { + this.impl = impl = VirtualThreadSchedulerImpls.create(); + } + return Map.of("jdk.management:type=VirtualThreadScheduler", impl); + } + }); + /** * OperatingSystemMXBean */ diff --git a/src/jdk.management/share/classes/com/sun/management/internal/VirtualThreadSchedulerImpls.java b/src/jdk.management/share/classes/com/sun/management/internal/VirtualThreadSchedulerImpls.java new file mode 100644 index 00000000000..8787237f903 --- /dev/null +++ b/src/jdk.management/share/classes/com/sun/management/internal/VirtualThreadSchedulerImpls.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.sun.management.internal; + +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; +import javax.management.ObjectName; +import jdk.management.VirtualThreadSchedulerMXBean; +import jdk.internal.access.JavaLangAccess; +import jdk.internal.access.SharedSecrets; +import jdk.internal.vm.ContinuationSupport; +import sun.management.Util; + +/** + * Provides the implementation of the management interface for the JDK's default virtual + * thread scheduler. + */ +public class VirtualThreadSchedulerImpls { + private VirtualThreadSchedulerImpls() { + } + + public static VirtualThreadSchedulerMXBean create() { + if (ContinuationSupport.isSupported()) { + return new VirtualThreadSchedulerImpl(); + } else { + return new BoundVirtualThreadSchedulerImpl(); + } + } + + /** + * Base implementation of VirtualThreadSchedulerMXBean. + */ + private abstract static class BaseVirtualThreadSchedulerImpl + implements VirtualThreadSchedulerMXBean { + + abstract void implSetParallelism(int size); + + @Override + public final void setParallelism(int size) { + Util.checkControlAccess(); + implSetParallelism(size); + } + + @Override + public final ObjectName getObjectName() { + return Util.newObjectName("jdk.management:type=VirtualThreadScheduler"); + } + + @Override + public String toString() { + var sb = new StringBuilder("[parallelism="); + sb.append(getParallelism()); + append(sb, "size", getPoolSize()); + append(sb, "mounted", getMountedVirtualThreadCount()); + append(sb, "queued", getQueuedVirtualThreadCount()); + sb.append(']'); + return sb.toString(); + } + + private void append(StringBuilder sb, String name, long value) { + sb.append(", ").append(name).append('='); + if (value >= 0) { + sb.append(value); + } else { + sb.append(""); + } + } + } + + /** + * Implementation of VirtualThreadSchedulerMXBean when virtual threads are + * implemented with continuations + scheduler. + */ + private static final class VirtualThreadSchedulerImpl extends BaseVirtualThreadSchedulerImpl { + /** + * Holder class for scheduler. + */ + private static class Scheduler { + private static final Executor scheduler = + SharedSecrets.getJavaLangAccess().virtualThreadDefaultScheduler(); + static Executor instance() { + return scheduler; + } + } + + @Override + public int getParallelism() { + if (Scheduler.instance() instanceof ForkJoinPool pool) { + return pool.getParallelism(); + } + throw new InternalError(); // should not get here + } + + @Override + void implSetParallelism(int size) { + if (Scheduler.instance() instanceof ForkJoinPool pool) { + pool.setParallelism(size); + if (pool.getPoolSize() < size) { + // FJ worker thread creation is on-demand + Thread.startVirtualThread(() -> { }); + } + + return; + } + throw new UnsupportedOperationException(); // should not get here + } + + @Override + public int getPoolSize() { + if (Scheduler.instance() instanceof ForkJoinPool pool) { + return pool.getPoolSize(); + } + return -1; // should not get here + } + + @Override + public int getMountedVirtualThreadCount() { + if (Scheduler.instance() instanceof ForkJoinPool pool) { + return pool.getActiveThreadCount(); + } + return -1; // should not get here + } + + @Override + public long getQueuedVirtualThreadCount() { + if (Scheduler.instance() instanceof ForkJoinPool pool) { + return pool.getQueuedTaskCount() + pool.getQueuedSubmissionCount(); + } + return -1L; // should not get here + } + } + + /** + * Implementation of VirtualThreadSchedulerMXBean when virtual threads are backed + * by platform threads. + */ + private static final class BoundVirtualThreadSchedulerImpl extends BaseVirtualThreadSchedulerImpl { + @Override + public int getParallelism() { + return Integer.MAX_VALUE; + } + + @Override + void implSetParallelism(int size) { + throw new UnsupportedOperationException(); + } + + @Override + public int getPoolSize() { + return -1; + } + + @Override + public int getMountedVirtualThreadCount() { + return -1; + } + + @Override + public long getQueuedVirtualThreadCount() { + return -1L; + } + } +} \ No newline at end of file diff --git a/src/jdk.management/share/classes/com/sun/management/package-info.java b/src/jdk.management/share/classes/com/sun/management/package-info.java index 0af2f33e8c7..453b4c1517f 100644 --- a/src/jdk.management/share/classes/com/sun/management/package-info.java +++ b/src/jdk.management/share/classes/com/sun/management/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 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 @@ -24,9 +24,8 @@ */ /** - * This package contains the JDK's extension to - * the standard implementation of the - * {@link java.lang.management} API and also defines the management + * This package contains JDK extensions to the standard implementation of + * the {@link java.lang.management} API and also defines the management * interface for some other components of the platform. * *

diff --git a/src/jdk.management/share/classes/jdk/management/VirtualThreadSchedulerMXBean.java b/src/jdk.management/share/classes/jdk/management/VirtualThreadSchedulerMXBean.java new file mode 100644 index 00000000000..556c5184ffe --- /dev/null +++ b/src/jdk.management/share/classes/jdk/management/VirtualThreadSchedulerMXBean.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.management; + +import java.lang.management.ManagementFactory; +import java.lang.management.PlatformManagedObject; +import java.util.concurrent.ForkJoinPool; +import javax.management.MBeanServer; +import javax.management.ObjectName; + +/** + * Management interface for the JDK's {@linkplain Thread##virtual-threads virtual thread} + * scheduler. + * + *

{@code VirtualThreadSchedulerMXBean} supports monitoring of the virtual thread + * scheduler's target parallelism, the {@linkplain Thread##platform-threads platform threads} + * used by the scheduler, and the number of virtual threads queued to the scheduler. It + * also supports dynamically changing the scheduler's target parallelism. + * + *

The management interface is registered with the platform {@link MBeanServer + * MBeanServer}. The {@link ObjectName ObjectName} that uniquely identifies the management + * interface within the {@code MBeanServer} is: "jdk.management:type=VirtualThreadScheduler". + * + *

Direct access to the MXBean interface can be obtained with + * {@link ManagementFactory#getPlatformMXBean(Class)}. + * + * @since 24 + */ +public interface VirtualThreadSchedulerMXBean extends PlatformManagedObject { + + /** + * {@return the scheduler's target parallelism} + * + * @see ForkJoinPool#getParallelism() + */ + int getParallelism(); + + /** + * Sets the scheduler's target parallelism. + * + *

Increasing the target parallelism allows the scheduler to use more platform + * threads to carry virtual threads if required. Decreasing the target parallelism + * reduces the number of threads that the scheduler may use to carry virtual threads. + * + * @apiNote If virtual threads are mounting and unmounting frequently then downward + * adjustment of the target parallelism will likely come into effect quickly. + * + * @implNote The JDK's virtual thread scheduler is a {@link ForkJoinPool}. Target + * parallelism defaults to the number of {@linkplain Runtime#availableProcessors() + * available processors}. The minimum target parallelism is 1, the maximum target + * parallelism is 32767. + * + * @param size the target parallelism level + * @throws IllegalArgumentException if size is less than the minimum, or + * greater than the maximum, supported by the scheduler + * @throws UnsupportedOperationException if changing the target + * parallelism is not suppored by the scheduler + * + * @see ForkJoinPool#setParallelism(int) + */ + void setParallelism(int size); + + /** + * {@return the current number of platform threads that the scheduler has started + * but have not terminated; {@code -1} if not known} + * + *

The count includes the platform threads that are currently carrying + * virtual threads and the platform threads that are not currently carrying virtual + * threads. The thread count may be greater than the scheduler's target parallelism. + * + * @implNote The JDK's virtual thread scheduler is a {@link ForkJoinPool}. The pool + * size is the {@linkplain ForkJoinPool#getPoolSize() number of worker threads}. + */ + int getPoolSize(); + + /** + * {@return an estimate of the number of virtual threads that are currently + * mounted by the scheduler; {@code -1} if not known} + * + *

The number of mounted virtual threads is equal to the number of platform + * threads carrying virtual threads. + * + * @implNote This method may overestimate the number of virtual threads that are mounted. + */ + int getMountedVirtualThreadCount(); + + /** + * {@return an estimate of the number of virtual threads that are queued to + * the scheduler to start or continue execution; {@code -1} if not known} + * + * @implNote This method may overestimate the number of virtual threads that are + * queued to execute. + */ + long getQueuedVirtualThreadCount(); +} \ No newline at end of file diff --git a/src/jdk.management/share/classes/jdk/management/package-info.java b/src/jdk.management/share/classes/jdk/management/package-info.java new file mode 100644 index 00000000000..f34a8e76373 --- /dev/null +++ b/src/jdk.management/share/classes/jdk/management/package-info.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * This package contains JDK extensions to the standard implementation of the + * {@link java.lang.management} API. + * + * @since 24 + */ + +package jdk.management; \ No newline at end of file diff --git a/src/jdk.management/share/classes/module-info.java b/src/jdk.management/share/classes/module-info.java index 092480de9e5..4fc747f55d4 100644 --- a/src/jdk.management/share/classes/module-info.java +++ b/src/jdk.management/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 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 @@ -23,9 +23,18 @@ * questions. */ +import java.lang.management.ManagementFactory; + /** * Defines JDK-specific management interfaces for the JVM. * + *

This module contains the JDK's extensions to the standard implementation + * of the {@link java.lang.management} API and also defines the management + * interfaces for some other components of the platform. + * + *

All platform MBeans are registered in the platform MBeanServer + * which can be obtained with {@link ManagementFactory#getPlatformMBeanServer}. + * * @moduleGraph * @since 9 */ @@ -33,6 +42,7 @@ module jdk.management { requires transitive java.management; exports com.sun.management; + exports jdk.management; provides sun.management.spi.PlatformMBeanProvider with com.sun.management.internal.PlatformMBeanProviderImpl; diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/GetThreadStateTest.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/GetThreadStateTest.java index cc8f5638c76..9ec9ca1b8c2 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/GetThreadStateTest.java +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/GetThreadState/GetThreadStateTest.java @@ -25,7 +25,7 @@ * @test id=default * @bug 8312498 * @summary Basic test for JVMTI GetThreadState with virtual threads - * @modules java.base/java.lang:+open + * @modules jdk.management * @library /test/lib * @run junit/othervm/native --enable-native-access=ALL-UNNAMED GetThreadStateTest */ @@ -33,7 +33,7 @@ /* * @test id=no-vmcontinuations * @requires vm.continuations - * @modules java.base/java.lang:+open + * @modules jdk.management * @library /test/lib * @run junit/othervm/native -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations --enable-native-access=ALL-UNNAMED GetThreadStateTest */ @@ -42,7 +42,7 @@ import java.util.StringJoiner; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.LockSupport; -import jdk.test.lib.thread.VThreadRunner; +import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management import jdk.test.lib.thread.VThreadPinner; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/VThreadEventTest.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/VThreadEventTest.java index 845e9adba01..5b4f6ac2a48 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/VThreadEventTest.java +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadEventTest/VThreadEventTest.java @@ -28,7 +28,7 @@ * @requires vm.continuations * @requires vm.jvmti * @requires vm.compMode != "Xcomp" - * @modules java.base/java.lang:+open + * @modules jdk.management * @library /test/lib * @run main/othervm/native * -Djdk.attach.allowAttachSelf=true -XX:+EnableDynamicAgentLoading VThreadEventTest attach diff --git a/test/jdk/TEST.groups b/test/jdk/TEST.groups index 97b355fbea6..0c6b13fdca0 100644 --- a/test/jdk/TEST.groups +++ b/test/jdk/TEST.groups @@ -262,6 +262,7 @@ jdk_text = \ jdk_management = \ java/lang/management \ + jdk/management \ com/sun/management \ sun/management \ jdk/internal/agent @@ -287,6 +288,7 @@ jdk_launcher = \ jdk_loom = \ com/sun/management/HotSpotDiagnosticMXBean \ com/sun/management/ThreadMXBean \ + jdk/management/VirtualThreadSchedulerMXBean \ java/lang/Thread \ java/lang/ThreadGroup \ java/lang/management/ThreadMXBean \ diff --git a/test/jdk/java/lang/Thread/virtual/JfrEvents.java b/test/jdk/java/lang/Thread/virtual/JfrEvents.java index 539ac2104ed..bc952331f64 100644 --- a/test/jdk/java/lang/Thread/virtual/JfrEvents.java +++ b/test/jdk/java/lang/Thread/virtual/JfrEvents.java @@ -25,7 +25,7 @@ * @test * @summary Basic test for JFR jdk.VirtualThreadXXX events * @requires vm.continuations - * @modules jdk.jfr java.base/java.lang:+open + * @modules jdk.jfr java.base/java.lang:+open jdk.management * @library /test/lib * @run junit/othervm --enable-native-access=ALL-UNNAMED JfrEvents */ @@ -50,7 +50,7 @@ import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordingFile; import jdk.test.lib.thread.VThreadPinner; -import jdk.test.lib.thread.VThreadRunner; +import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management import jdk.test.lib.thread.VThreadScheduler; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeAll; diff --git a/test/jdk/java/lang/Thread/virtual/MonitorEnterExit.java b/test/jdk/java/lang/Thread/virtual/MonitorEnterExit.java index d1f2bf0c1bf..c2389ab7ddd 100644 --- a/test/jdk/java/lang/Thread/virtual/MonitorEnterExit.java +++ b/test/jdk/java/lang/Thread/virtual/MonitorEnterExit.java @@ -24,7 +24,7 @@ /* * @test id=default * @summary Test virtual thread with monitor enter/exit - * @modules java.base/java.lang:+open + * @modules java.base/java.lang:+open jdk.management * @library /test/lib * @run junit/othervm --enable-native-access=ALL-UNNAMED MonitorEnterExit */ @@ -43,7 +43,7 @@ import java.util.stream.IntStream; import java.util.stream.Stream; import jdk.test.lib.thread.VThreadPinner; -import jdk.test.lib.thread.VThreadRunner; +import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management import jdk.test.lib.thread.VThreadScheduler; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeAll; diff --git a/test/jdk/java/lang/Thread/virtual/MonitorWaitNotify.java b/test/jdk/java/lang/Thread/virtual/MonitorWaitNotify.java index 5f730dbbf0a..81f1c1ea97f 100644 --- a/test/jdk/java/lang/Thread/virtual/MonitorWaitNotify.java +++ b/test/jdk/java/lang/Thread/virtual/MonitorWaitNotify.java @@ -24,7 +24,7 @@ /* * @test id=default * @summary Test virtual threads using Object.wait/notifyAll - * @modules java.base/java.lang:+open + * @modules java.base/java.lang:+open jdk.management * @library /test/lib * @run junit/othervm --enable-native-access=ALL-UNNAMED MonitorWaitNotify */ @@ -46,8 +46,7 @@ import java.util.stream.Stream; import java.util.stream.Collectors; import jdk.test.lib.thread.VThreadScheduler; -import jdk.test.lib.thread.VThreadRunner; -import jdk.test.lib.thread.VThreadRunner; +import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management import jdk.test.lib.thread.VThreadPinner; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeAll; diff --git a/test/jdk/java/lang/Thread/virtual/ThreadAPI.java b/test/jdk/java/lang/Thread/virtual/ThreadAPI.java index 3bb5c75a1b5..cda302f391f 100644 --- a/test/jdk/java/lang/Thread/virtual/ThreadAPI.java +++ b/test/jdk/java/lang/Thread/virtual/ThreadAPI.java @@ -25,7 +25,7 @@ * @test id=default * @bug 8284161 8286788 8321270 * @summary Test Thread API with virtual threads - * @modules java.base/java.lang:+open + * @modules java.base/java.lang:+open jdk.management * @library /test/lib * @run junit/othervm --enable-native-access=ALL-UNNAMED ThreadAPI */ @@ -33,7 +33,7 @@ /* * @test id=no-vmcontinuations * @requires vm.continuations - * @modules java.base/java.lang:+open + * @modules java.base/java.lang:+open jdk.management * @library /test/lib * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations * --enable-native-access=ALL-UNNAMED ThreadAPI @@ -62,7 +62,7 @@ import java.util.stream.Stream; import java.nio.channels.Selector; import jdk.test.lib.thread.VThreadPinner; -import jdk.test.lib.thread.VThreadRunner; +import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management import jdk.test.lib.thread.VThreadScheduler; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeAll; diff --git a/test/jdk/java/lang/Thread/virtual/VirtualThreadPinnedEventThrows.java b/test/jdk/java/lang/Thread/virtual/VirtualThreadPinnedEventThrows.java index 0c5f1c3d6b4..2d822655428 100644 --- a/test/jdk/java/lang/Thread/virtual/VirtualThreadPinnedEventThrows.java +++ b/test/jdk/java/lang/Thread/virtual/VirtualThreadPinnedEventThrows.java @@ -24,7 +24,7 @@ /** * @test * @summary Test parking when pinned and emitting the JFR VirtualThreadPinnedEvent throws - * @modules java.base/java.lang:+open java.base/jdk.internal.event + * @modules java.base/java.lang:+open java.base/jdk.internal.event jdk.management * @library /test/lib * @compile/module=java.base jdk/internal/event/VirtualThreadPinnedEvent.java * @run junit/othervm --enable-native-access=ALL-UNNAMED VirtualThreadPinnedEventThrows @@ -36,7 +36,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.LockSupport; import jdk.internal.event.VirtualThreadPinnedEvent; -import jdk.test.lib.thread.VThreadRunner; +import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management import jdk.test.lib.thread.VThreadPinner; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeAll; diff --git a/test/jdk/java/lang/Thread/virtual/stress/GetStackTraceALotWhenBlocking.java b/test/jdk/java/lang/Thread/virtual/stress/GetStackTraceALotWhenBlocking.java index a5aea9ee398..5e15ea083e4 100644 --- a/test/jdk/java/lang/Thread/virtual/stress/GetStackTraceALotWhenBlocking.java +++ b/test/jdk/java/lang/Thread/virtual/stress/GetStackTraceALotWhenBlocking.java @@ -26,7 +26,7 @@ * @summary Stress test Thread.getStackTrace on virtual threads that are blocking or * blocked on monitorenter * @requires vm.debug != true - * @modules java.base/java.lang:+open + * @modules jdk.management * @library /test/lib * @run main/othervm GetStackTraceALotWhenBlocking 500000 */ @@ -34,7 +34,7 @@ /* * @test * @requires vm.debug == true & vm.continuations - * @modules java.base/java.lang:+open + * @modules jdk.management * @library /test/lib * @run main/othervm/timeout=300 GetStackTraceALotWhenBlocking 50000 */ @@ -42,7 +42,7 @@ import java.time.Instant; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; -import jdk.test.lib.thread.VThreadRunner; +import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management public class GetStackTraceALotWhenBlocking { diff --git a/test/jdk/java/lang/Thread/virtual/stress/GetStackTraceALotWhenPinned.java b/test/jdk/java/lang/Thread/virtual/stress/GetStackTraceALotWhenPinned.java index d7e6c3a8de4..260446a1e3d 100644 --- a/test/jdk/java/lang/Thread/virtual/stress/GetStackTraceALotWhenPinned.java +++ b/test/jdk/java/lang/Thread/virtual/stress/GetStackTraceALotWhenPinned.java @@ -26,7 +26,7 @@ * @bug 8322818 * @summary Stress test Thread.getStackTrace on a virtual thread that is pinned * @requires vm.debug != true - * @modules java.base/java.lang:+open + * @modules jdk.management * @library /test/lib * @run main/othervm --enable-native-access=ALL-UNNAMED GetStackTraceALotWhenPinned 500000 */ @@ -34,7 +34,7 @@ /* * @test * @requires vm.debug == true - * @modules java.base/java.lang:+open + * @modules jdk.management * @library /test/lib * @run main/othervm/timeout=300 --enable-native-access=ALL-UNNAMED GetStackTraceALotWhenPinned 200000 */ @@ -42,7 +42,7 @@ import java.time.Instant; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.LockSupport; -import jdk.test.lib.thread.VThreadRunner; +import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management import jdk.test.lib.thread.VThreadPinner; public class GetStackTraceALotWhenPinned { diff --git a/test/jdk/java/lang/management/ThreadMXBean/VirtualThreadDeadlocks.java b/test/jdk/java/lang/management/ThreadMXBean/VirtualThreadDeadlocks.java index d4da6073337..91b31c41be3 100644 --- a/test/jdk/java/lang/management/ThreadMXBean/VirtualThreadDeadlocks.java +++ b/test/jdk/java/lang/management/ThreadMXBean/VirtualThreadDeadlocks.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -26,7 +26,7 @@ * @bug 8284161 8287103 * @summary Test ThredMXBean.findMonitorDeadlockedThreads with cycles of * platform and virtual threads in deadlock - * @modules java.base/java.lang:+open java.management + * @modules java.management jdk.management * @library /test/lib * @run main/othervm VirtualThreadDeadlocks PP * @run main/othervm VirtualThreadDeadlocks PV @@ -36,7 +36,7 @@ /** * @test id=no-vmcontinuations * @requires vm.continuations - * @modules java.base/java.lang:+open java.management + * @modules java.management jdk.management * @library /test/lib * @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations VirtualThreadDeadlocks PP * @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations VirtualThreadDeadlocks PV @@ -48,7 +48,7 @@ import java.lang.management.ThreadMXBean; import java.util.Arrays; import java.util.concurrent.CyclicBarrier; import java.util.stream.Stream; -import jdk.test.lib.thread.VThreadRunner; +import jdk.test.lib.thread.VThreadRunner; // ensureParallelism requires jdk.management public class VirtualThreadDeadlocks { private static final Object LOCK1 = new Object(); diff --git a/test/jdk/jdk/management/VirtualThreadSchedulerMXBean/VirtualThreadSchedulerMXBeanTest.java b/test/jdk/jdk/management/VirtualThreadSchedulerMXBean/VirtualThreadSchedulerMXBeanTest.java new file mode 100644 index 00000000000..891f6e62e6f --- /dev/null +++ b/test/jdk/jdk/management/VirtualThreadSchedulerMXBean/VirtualThreadSchedulerMXBeanTest.java @@ -0,0 +1,260 @@ +/* + * Copyright (c) 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 + * 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 + * @bug 8338890 + * @summary Basic test for jdk.management.VirtualThreadSchedulerMXBean + * @requires vm.continuations + * @modules jdk.management + * @library /test/lib + * @run junit/othervm VirtualThreadSchedulerMXBeanTest + */ + +import java.lang.management.ManagementFactory; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; +import java.util.stream.IntStream; +import javax.management.MBeanServer; +import jdk.management.VirtualThreadSchedulerMXBean; + +import jdk.test.lib.thread.VThreadRunner; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assumptions.*; + +class VirtualThreadSchedulerMXBeanTest { + + /** + * VirtualThreadSchedulerMXBean objects to test. + */ + private static Stream managedBeans() throws Exception { + var bean1 = ManagementFactory.getPlatformMXBean(VirtualThreadSchedulerMXBean.class); + + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + var bean2 = ManagementFactory.newPlatformMXBeanProxy(server, + "jdk.management:type=VirtualThreadScheduler", + VirtualThreadSchedulerMXBean.class); + + return Stream.of(bean1, bean2); + } + + /** + * Test default parallelism. + */ + @ParameterizedTest + @MethodSource("managedBeans") + void testDefaultParallelism(VirtualThreadSchedulerMXBean bean) { + assertEquals(Runtime.getRuntime().availableProcessors(), bean.getParallelism()); + } + + /** + * Test increasing parallelism. + */ + @ParameterizedTest + @MethodSource("managedBeans") + void testIncreaseParallelism(VirtualThreadSchedulerMXBean bean) throws Exception { + assumeFalse(Thread.currentThread().isVirtual(), "Main thread is a virtual thread"); + + final int parallelism = bean.getParallelism(); + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + var done = new AtomicBoolean(); + Runnable busyTask = () -> { + while (!done.get()) { + Thread.onSpinWait(); + } + }; + + try { + // saturate + IntStream.range(0, parallelism).forEach(_ -> executor.submit(busyTask)); + awaitPoolSizeGte(bean, parallelism); + awaitMountedVirtualThreadCountGte(bean, parallelism); + + // increase parallelism + for (int k = 1; k <= 4; k++) { + int newParallelism = parallelism + k; + bean.setParallelism(newParallelism); + executor.submit(busyTask); + + // pool size and mounted virtual thread should increase + awaitPoolSizeGte(bean, newParallelism); + awaitMountedVirtualThreadCountGte(bean, newParallelism); + } + } finally { + done.set(true); + } + } finally { + bean.setParallelism(parallelism); // restore + } + } + + /** + * Test reducing parallelism. + */ + @ParameterizedTest + @MethodSource("managedBeans") + void testReduceParallelism(VirtualThreadSchedulerMXBean bean) throws Exception { + assumeFalse(Thread.currentThread().isVirtual(), "Main thread is a virtual thread"); + + final int parallelism = bean.getParallelism(); + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + var done = new AtomicBoolean(); + var sleep = new AtomicBoolean(); + + // spin when !sleep + Runnable busyTask = () -> { + while (!done.get()) { + if (sleep.get()) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { } + } else { + Thread.onSpinWait(); + } + } + }; + + try { + // increase parallelism + saturate + int newParallelism = parallelism + 4; + bean.setParallelism(newParallelism); + IntStream.range(0, newParallelism).forEach(_ -> executor.submit(busyTask)); + awaitMountedVirtualThreadCountGte(bean, newParallelism); + + // reduce parallelism and workload + newParallelism = Math.clamp(parallelism / 2, 1, parallelism); + bean.setParallelism(newParallelism); + sleep.set(true); + // mounted virtual thread count should reduce + awaitMountedVirtualThreadCountLte(bean, newParallelism); + + // increase workload, the mounted virtual thread count should not increase + sleep.set(false); + for (int i = 0; i < 5; i++) { + Thread.sleep(100); + assertTrue(bean.getMountedVirtualThreadCount() <= newParallelism); + } + + } finally { + done.set(true); + } + } finally { + bean.setParallelism(parallelism); // restore + } + } + + /** + * Test getPoolSize. + */ + @ParameterizedTest + @MethodSource("managedBeans") + void testPoolSize(VirtualThreadSchedulerMXBean bean) { + assertTrue(bean.getPoolSize() >= 0); + VThreadRunner.run(() -> { + assertTrue(Thread.currentThread().isVirtual()); + assertTrue(bean.getPoolSize() >= 1); + }); + } + + /** + * Test getMountedVirtualThreadCount. + */ + @ParameterizedTest + @MethodSource("managedBeans") + void testMountedVirtualThreadCount(VirtualThreadSchedulerMXBean bean) { + assertTrue(bean.getMountedVirtualThreadCount() >= 0); + VThreadRunner.run(() -> { + assertTrue(Thread.currentThread().isVirtual()); + assertTrue(bean.getMountedVirtualThreadCount() >= 1); + }); + } + + /** + * Test getQueuedVirtualThreadCount. + */ + @ParameterizedTest + @MethodSource("managedBeans") + void testQueuedVirtualThreadCount(VirtualThreadSchedulerMXBean bean) throws Exception { + assumeFalse(Thread.currentThread().isVirtual(), "Main thread is a virtual thread"); + + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + var done = new AtomicBoolean(); + Runnable busyTask = () -> { + while (!done.get()) { + Thread.onSpinWait(); + } + }; + + try { + // saturate + int parallelism = bean.getParallelism(); + IntStream.range(0, parallelism).forEach(_ -> executor.submit(busyTask)); + awaitMountedVirtualThreadCountGte(bean, parallelism); + + // start 5 virtual threads, their tasks will be queued to execute + for (int i = 0; i < 5; i++) { + executor.submit(() -> { }); + } + assertTrue(bean.getQueuedVirtualThreadCount() >= 5); + } finally { + done.set(true); + } + } + } + + /** + * Waits for pool size >= target to be true. + */ + void awaitPoolSizeGte(VirtualThreadSchedulerMXBean bean, int target) throws InterruptedException { + System.err.format("await pool size >= %d ...%n", target); + while (bean.getPoolSize() < target) { + Thread.sleep(10); + } + } + + /** + * Waits for the mounted virtual thread count >= target to be true. + */ + void awaitMountedVirtualThreadCountGte(VirtualThreadSchedulerMXBean bean, + int target) throws InterruptedException { + System.err.format("await mounted virtual thread count >= %d ...%n", target); + while (bean.getMountedVirtualThreadCount() < target) { + Thread.sleep(10); + } + } + + /** + * Waits for the mounted virtual thread count <= target to be true. + */ + void awaitMountedVirtualThreadCountLte(VirtualThreadSchedulerMXBean bean, + int target) throws InterruptedException { + System.err.format("await mounted virtual thread count <= %d ...%n", target); + while (bean.getMountedVirtualThreadCount() > target) { + Thread.sleep(10); + } + } +} \ No newline at end of file diff --git a/test/lib/jdk/test/lib/thread/VThreadRunner.java b/test/lib/jdk/test/lib/thread/VThreadRunner.java index ba69496a047..1b09e5e0d12 100644 --- a/test/lib/jdk/test/lib/thread/VThreadRunner.java +++ b/test/lib/jdk/test/lib/thread/VThreadRunner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -23,10 +23,10 @@ package jdk.test.lib.thread; -import java.lang.reflect.Field; +import java.lang.management.ManagementFactory; import java.time.Duration; -import java.util.concurrent.ForkJoinPool; import java.util.concurrent.atomic.AtomicReference; +import jdk.management.VirtualThreadSchedulerMXBean; /** * Helper class to support tests running tasks in a virtual thread. @@ -133,39 +133,36 @@ public class VThreadRunner { run(null, 0, task); } - /** - * Returns the virtual thread scheduler. - */ - private static ForkJoinPool defaultScheduler() { - try { - var clazz = Class.forName("java.lang.VirtualThread"); - var field = clazz.getDeclaredField("DEFAULT_SCHEDULER"); - field.setAccessible(true); - return (ForkJoinPool) field.get(null); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - /** * Sets the virtual thread scheduler's target parallelism. + * + *

Tests using this method should use "{@code @modules jdk.management}" to help + * test selection. + * * @return the previous parallelism level */ public static int setParallelism(int size) { - return defaultScheduler().setParallelism(size); + var bean = ManagementFactory.getPlatformMXBean(VirtualThreadSchedulerMXBean.class); + int parallelism = bean.getParallelism(); + bean.setParallelism(size); + return parallelism; } /** * Ensures that the virtual thread scheduler's target parallelism is at least * the given size. If the target parallelism is less than the given size then * it is changed to the given size. + * + *

Tests using this method should use "{@code @modules jdk.management}" to help + * test selection. + * * @return the previous parallelism level */ public static int ensureParallelism(int size) { - ForkJoinPool pool = defaultScheduler(); - int parallelism = pool.getParallelism(); + var bean = ManagementFactory.getPlatformMXBean(VirtualThreadSchedulerMXBean.class); + int parallelism = bean.getParallelism(); if (size > parallelism) { - pool.setParallelism(size); + bean.setParallelism(size); } return parallelism; }