Skip to content

Commit 5c8cb2e

Browse files
author
Alan Bateman
committed
8337199: Add jcmd Thread.vthread_scheduler and Thread.vthread_pollers diagnostic commands
Reviewed-by: dholmes, kevinw
1 parent 3eb5461 commit 5c8cb2e

File tree

11 files changed

+383
-12
lines changed

11 files changed

+383
-12
lines changed

src/hotspot/share/classfile/vmSymbols.hpp

+6-1
Original file line numberDiff line numberDiff line change
@@ -739,10 +739,15 @@ class SerializeClosure;
739739
template(toFileURL_signature, "(Ljava/lang/String;)Ljava/net/URL;") \
740740
template(url_array_classloader_void_signature, "([Ljava/net/URL;Ljava/lang/ClassLoader;)V") \
741741
\
742-
/* Thread.dump_to_file jcmd */ \
742+
/* jcmd Thread.dump_to_file */ \
743743
template(jdk_internal_vm_ThreadDumper, "jdk/internal/vm/ThreadDumper") \
744744
template(dumpThreads_name, "dumpThreads") \
745745
template(dumpThreadsToJson_name, "dumpThreadsToJson") \
746+
\
747+
/* jcmd Thread.vthread_scheduler and Thread.vthread_pollers */ \
748+
template(jdk_internal_vm_JcmdVThreadCommands, "jdk/internal/vm/JcmdVThreadCommands") \
749+
template(printScheduler_name, "printScheduler") \
750+
template(printPollers_name, "printPollers") \
746751

747752
/*end*/
748753

src/hotspot/share/services/diagnosticCommand.cpp

+41-7
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ void DCmd::register_dcmds(){
128128
#endif // INCLUDE_JVMTI
129129
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ThreadDumpDCmd>(full_export, true, false));
130130
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ThreadDumpToFileDCmd>(full_export, true, false));
131+
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<VThreadSchedulerDCmd>(full_export, true, false));
132+
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<VThreadPollersDCmd>(full_export, true, false));
131133
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ClassLoaderStatsDCmd>(full_export, true, false));
132134
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<ClassLoaderHierarchyDCmd>(full_export, true, false));
133135
DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl<CompileQueueDCmd>(full_export, true, false));
@@ -1073,13 +1075,6 @@ void ThreadDumpToFileDCmd::dumpToFile(Symbol* name, Symbol* signature, const cha
10731075

10741076
Symbol* sym = vmSymbols::jdk_internal_vm_ThreadDumper();
10751077
Klass* k = SystemDictionary::resolve_or_fail(sym, true, CHECK);
1076-
InstanceKlass* ik = InstanceKlass::cast(k);
1077-
if (HAS_PENDING_EXCEPTION) {
1078-
java_lang_Throwable::print(PENDING_EXCEPTION, output());
1079-
output()->cr();
1080-
CLEAR_PENDING_EXCEPTION;
1081-
return;
1082-
}
10831078

10841079
// invoke the ThreadDump method to dump to file
10851080
JavaValue result(T_OBJECT);
@@ -1110,6 +1105,45 @@ void ThreadDumpToFileDCmd::dumpToFile(Symbol* name, Symbol* signature, const cha
11101105
output()->print_raw((const char*)addr, ba->length());
11111106
}
11121107

1108+
// Calls a static no-arg method on jdk.internal.vm.JcmdVThreadCommands that returns a byte[] with
1109+
// the output. If the method completes successfully then the bytes are copied to the output stream.
1110+
// If the method fails then the exception is printed to the output stream.
1111+
static void execute_vthread_command(Symbol* method_name, outputStream* output, TRAPS) {
1112+
ResourceMark rm(THREAD);
1113+
HandleMark hm(THREAD);
1114+
1115+
Klass* k = SystemDictionary::resolve_or_fail(vmSymbols::jdk_internal_vm_JcmdVThreadCommands(), true, CHECK);
1116+
1117+
JavaValue result(T_OBJECT);
1118+
JavaCallArguments args;
1119+
JavaCalls::call_static(&result,
1120+
k,
1121+
method_name,
1122+
vmSymbols::void_byte_array_signature(),
1123+
&args,
1124+
THREAD);
1125+
if (HAS_PENDING_EXCEPTION) {
1126+
java_lang_Throwable::print(PENDING_EXCEPTION, output);
1127+
output->cr();
1128+
CLEAR_PENDING_EXCEPTION;
1129+
return;
1130+
}
1131+
1132+
// copy the bytes to the output stream
1133+
oop res = cast_to_oop(result.get_jobject());
1134+
typeArrayOop ba = typeArrayOop(res);
1135+
jbyte* addr = typeArrayOop(res)->byte_at_addr(0);
1136+
output->print_raw((const char*)addr, ba->length());
1137+
}
1138+
1139+
void VThreadSchedulerDCmd::execute(DCmdSource source, TRAPS) {
1140+
execute_vthread_command(vmSymbols::printScheduler_name(), output(), CHECK);
1141+
}
1142+
1143+
void VThreadPollersDCmd::execute(DCmdSource source, TRAPS) {
1144+
execute_vthread_command(vmSymbols::printPollers_name(), output(), CHECK);
1145+
}
1146+
11131147
CompilationMemoryStatisticDCmd::CompilationMemoryStatisticDCmd(outputStream* output, bool heap) :
11141148
DCmdWithParser(output, heap),
11151149
_human_readable("-H", "Human readable format", "BOOLEAN", false, "false"),

src/hotspot/share/services/diagnosticCommand.hpp

+27
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,33 @@ class ThreadDumpToFileDCmd : public DCmdWithParser {
776776
virtual void execute(DCmdSource source, TRAPS);
777777
};
778778

779+
class VThreadSchedulerDCmd : public DCmd {
780+
public:
781+
VThreadSchedulerDCmd(outputStream* output, bool heap) : DCmd(output, heap) { }
782+
static const char* name() {
783+
return "Thread.vthread_scheduler";
784+
}
785+
static const char* description() {
786+
return "Print the virtual thread scheduler, and the delayed task schedulers that support "
787+
"virtual threads doing timed operations.";
788+
}
789+
static const char* impact() { return "Low"; }
790+
virtual void execute(DCmdSource source, TRAPS);
791+
};
792+
793+
class VThreadPollersDCmd : public DCmd {
794+
public:
795+
VThreadPollersDCmd(outputStream* output, bool heap) : DCmd(output, heap) { }
796+
static const char* name() {
797+
return "Thread.vthread_pollers";
798+
}
799+
static const char* description() {
800+
return "Print the I/O pollers that support virtual threads doing blocking network I/O operations.";
801+
}
802+
static const char* impact() { return "Low"; }
803+
virtual void execute(DCmdSource source, TRAPS);
804+
};
805+
779806
class CompilationMemoryStatisticDCmd: public DCmdWithParser {
780807
protected:
781808
DCmdArgument<bool> _human_readable;

src/java.base/share/classes/java/lang/System.java

+5
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import java.util.ResourceBundle;
5656
import java.util.Set;
5757
import java.util.concurrent.Executor;
58+
import java.util.concurrent.ScheduledExecutorService;
5859
import java.util.function.Supplier;
5960
import java.util.concurrent.ConcurrentHashMap;
6061
import java.util.stream.Stream;
@@ -2304,6 +2305,10 @@ public Executor virtualThreadDefaultScheduler() {
23042305
return VirtualThread.defaultScheduler();
23052306
}
23062307

2308+
public Stream<ScheduledExecutorService> virtualThreadDelayedTaskSchedulers() {
2309+
return VirtualThread.delayedTaskSchedulers();
2310+
}
2311+
23072312
public StackWalker newStackWalkerInstance(Set<StackWalker.Option> options,
23082313
ContinuationScope contScope,
23092314
Continuation continuation) {

src/java.base/share/classes/java/lang/VirtualThread.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*/
2525
package java.lang;
2626

27+
import java.util.Arrays;
2728
import java.util.Locale;
2829
import java.util.Objects;
2930
import java.util.concurrent.CountDownLatch;
@@ -32,12 +33,12 @@
3233
import java.util.concurrent.ForkJoinPool;
3334
import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory;
3435
import java.util.concurrent.ForkJoinTask;
35-
import java.util.concurrent.ForkJoinWorkerThread;
3636
import java.util.concurrent.Future;
3737
import java.util.concurrent.RejectedExecutionException;
3838
import java.util.concurrent.ScheduledExecutorService;
3939
import java.util.concurrent.ScheduledThreadPoolExecutor;
4040
import java.util.concurrent.TimeUnit;
41+
import java.util.stream.Stream;
4142
import jdk.internal.event.VirtualThreadEndEvent;
4243
import jdk.internal.event.VirtualThreadStartEvent;
4344
import jdk.internal.event.VirtualThreadSubmitFailedEvent;
@@ -192,6 +193,13 @@ static Executor defaultScheduler() {
192193
return DEFAULT_SCHEDULER;
193194
}
194195

196+
/**
197+
* Returns a stream of the delayed task schedulers used to support timed operations.
198+
*/
199+
static Stream<ScheduledExecutorService> delayedTaskSchedulers() {
200+
return Arrays.stream(DELAYED_TASK_SCHEDULERS);
201+
}
202+
195203
/**
196204
* Returns the continuation scope used for virtual threads.
197205
*/

src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java

+6
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import java.util.concurrent.ConcurrentHashMap;
4646
import java.util.concurrent.Executor;
4747
import java.util.concurrent.RejectedExecutionException;
48+
import java.util.concurrent.ScheduledExecutorService;
4849
import java.util.stream.Stream;
4950

5051
import jdk.internal.loader.NativeLibraries;
@@ -595,6 +596,11 @@ public interface JavaLangAccess {
595596
*/
596597
Executor virtualThreadDefaultScheduler();
597598

599+
/**
600+
* Returns a stream of the delayed task schedulers used for virtual threads.
601+
*/
602+
Stream<ScheduledExecutorService> virtualThreadDelayedTaskSchedulers();
603+
598604
/**
599605
* Creates a new StackWalker
600606
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package jdk.internal.vm;
26+
27+
import java.nio.charset.StandardCharsets;
28+
import java.util.List;
29+
import java.util.stream.IntStream;
30+
import jdk.internal.access.JavaLangAccess;
31+
import jdk.internal.access.SharedSecrets;
32+
import sun.nio.ch.Poller;
33+
34+
/**
35+
* The implementation for the jcmd Thread.vthread_* diagnostic commands. These methods are
36+
* called from the "Attach Listener" thread.
37+
*/
38+
public class JcmdVThreadCommands {
39+
private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
40+
41+
private JcmdVThreadCommands() { }
42+
43+
/**
44+
* Invoked by the VM to print the virtual scheduler to a byte[].
45+
*/
46+
private static byte[] printScheduler() {
47+
StringBuilder sb = new StringBuilder();
48+
49+
// virtual thread scheduler
50+
sb.append(JLA.virtualThreadDefaultScheduler())
51+
.append(System.lineSeparator());
52+
53+
// break
54+
sb.append(System.lineSeparator());
55+
56+
// delayed task schedulers
57+
sb.append("Delayed task schedulers:").append(System.lineSeparator());
58+
var delayedTaskSchedulers = JLA.virtualThreadDelayedTaskSchedulers().toList();
59+
IntStream.range(0, delayedTaskSchedulers.size())
60+
.forEach(i -> sb.append('[')
61+
.append(i)
62+
.append("] ")
63+
.append(delayedTaskSchedulers.get(i))
64+
.append(System.lineSeparator()));
65+
66+
return sb.toString().getBytes(StandardCharsets.UTF_8);
67+
}
68+
69+
/**
70+
* Invoked by the VM to print the I/O pollers to a byte[].
71+
*/
72+
private static byte[] printPollers() {
73+
StringBuilder sb = new StringBuilder();
74+
75+
Poller masterPoller = Poller.masterPoller();
76+
List<Poller> readPollers = Poller.readPollers();
77+
List<Poller> writePollers = Poller.writePollers();
78+
79+
if (masterPoller != null) {
80+
sb.append("Master I/O poller:")
81+
.append(System.lineSeparator())
82+
.append(masterPoller)
83+
.append(System.lineSeparator());
84+
85+
// break
86+
sb.append(System.lineSeparator());
87+
}
88+
89+
sb.append("Read I/O pollers:");
90+
sb.append(System.lineSeparator());
91+
IntStream.range(0, readPollers.size())
92+
.forEach(i -> sb.append('[')
93+
.append(i)
94+
.append("] ")
95+
.append(readPollers.get(i))
96+
.append(System.lineSeparator()));
97+
98+
// break
99+
sb.append(System.lineSeparator());
100+
101+
sb.append("Write I/O pollers:");
102+
sb.append(System.lineSeparator());
103+
IntStream.range(0, writePollers.size())
104+
.forEach(i -> sb.append('[')
105+
.append(i)
106+
.append("] ")
107+
.append(writePollers.get(i))
108+
.append(System.lineSeparator()));
109+
110+
return sb.toString().getBytes(StandardCharsets.UTF_8);
111+
}
112+
}

src/java.base/share/classes/sun/nio/ch/Poller.java

+29-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.util.concurrent.locks.LockSupport;
3737
import java.util.function.BooleanSupplier;
3838
import jdk.internal.misc.InnocuousThread;
39+
import jdk.internal.vm.annotation.Stable;
3940

4041
/**
4142
* Polls file descriptors. Virtual threads invoke the poll method to park
@@ -53,6 +54,9 @@ public abstract class Poller {
5354
}
5455
}
5556

57+
// the poller or sub-poller thread
58+
private @Stable Thread owner;
59+
5660
// maps file descriptors to parked Thread
5761
private final Map<Integer, Thread> map = new ConcurrentHashMap<>();
5862

@@ -238,6 +242,7 @@ private void wakeup(int fdVal) {
238242
* descriptor that is polled.
239243
*/
240244
private void pollerLoop() {
245+
owner = Thread.currentThread();
241246
try {
242247
for (;;) {
243248
poll(-1);
@@ -258,6 +263,7 @@ private void pollerLoop() {
258263
*/
259264
private void subPollerLoop(Poller masterPoller) {
260265
assert Thread.currentThread().isVirtual();
266+
owner = Thread.currentThread();
261267
try {
262268
int polled = 0;
263269
for (;;) {
@@ -282,7 +288,8 @@ public int registered() {
282288

283289
@Override
284290
public String toString() {
285-
return Objects.toIdentityString(this) + " [registered = " + registered() + "]";
291+
return String.format("%s [registered = %d, owner = %s]",
292+
Objects.toIdentityString(this), registered(), owner);
286293
}
287294

288295
/**
@@ -442,4 +449,25 @@ private void startPlatformThread(String name, Runnable task) {
442449
}
443450
}
444451
}
452+
453+
/**
454+
* Return the master poller or null if there is no master poller.
455+
*/
456+
public static Poller masterPoller() {
457+
return POLLERS.masterPoller();
458+
}
459+
460+
/**
461+
* Return the list of read pollers.
462+
*/
463+
public static List<Poller> readPollers() {
464+
return POLLERS.readPollers();
465+
}
466+
467+
/**
468+
* Return the list of write pollers.
469+
*/
470+
public static List<Poller> writePollers() {
471+
return POLLERS.writePollers();
472+
}
445473
}

src/jdk.jcmd/share/man/jcmd.md

+11
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,17 @@ The following commands are available:
737737
- `-e`: (Optional) Print extended thread information (BOOLEAN, false)
738738
- `-l`: (Optional) Prints `java.util.concurrent` locks (BOOLEAN, false)
739739

740+
`Thread.vthread_scheduler`
741+
: Print the virtual thread scheduler, and the delayed task schedulers that support
742+
virtual threads doing timed operations.
743+
744+
Impact: Low
745+
746+
`Thread.vthread_pollers`
747+
: Print the I/O pollers that support virtual threads doing blocking network I/O operations.
748+
749+
Impact: Low
750+
740751
`VM.cds` \[*arguments*\]
741752
: Dump a static or dynamic shared archive that includes all currently loaded classes.
742753

0 commit comments

Comments
 (0)