Учитывая этот код, он пытается прослушать событие jdk.VirtualThreadPinned, когда виртуальный поток закреплен:
import static org.junit.Assert.assertTrue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import jdk.jfr.Event;
import jdk.jfr.Name;
import jdk.jfr.consumer.RecordingStream;
import org.junit.Test;
public class JFRTest {
@Test
public void pinnedVirtualThreads() throws InterruptedException {
CountDownLatch stopListening = new CountDownLatch(1);
CountDownLatch startListening = new CountDownLatch(1);
try (RecordingStream recordingStream = new RecordingStream()) {
// Create and start the recording stream
startRecordingStream(recordingStream, startListening, stopListening);
// Make sure JFR listener is running before continue
while (true) {
new StartEvent().commit();
if (startListening.await(1, TimeUnit.SECONDS)) {
break;
}
}
// Simulate blocking operation
Thread vt = Thread.ofVirtual().start(() -> {
synchronized (this) {
System.out.println("Virtual Thread is pinned");
}
});
vt.join();
}
// Wait till the jdk.VirtualThreadPinned triggers
assertTrue("jdk.VirtualThreadPinned was not sent", stopListening.await(5, TimeUnit.SECONDS));
}
private void startRecordingStream(RecordingStream recordingStream, CountDownLatch startListening, CountDownLatch stopListening) {
// Enable the jdk.VirtualThreadPinned event
recordingStream.enable("jdk.VirtualThreadPinned").withStackTrace();
// Notify listener is running after receiving the StartEvent
recordingStream.onEvent("StartEvent", event -> {
System.out.println("Received " + event);
startListening.countDown();
});
// Set an event handler
recordingStream.onEvent("jdk.VirtualThreadPinned", event -> {
System.out.println("VirtualThreadPinned event detected!");
System.out.println("Timestamp: " + event);
stopListening.countDown();
});
// Start the recording stream
recordingStream.startAsync();
}
@Name("StartEvent")
private static class StartEvent extends Event {
}
}
Это результат:
[INFO] Running com.example.general.JFRTest
Received StartEvent {
startTime = 10:37:46.830 (2024-06-12)
eventThread = "main" (javaThreadId = 1)
stackTrace = [
com.example.general.JFRTest.pinnedVirtualThreads() line: 25
jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Object, Object[]) line: 103
java.lang.reflect.Method.invoke(Object, Object[]) line: 580
org.junit.runners.model.FrameworkMethod$1.runReflectiveCall() line: 59
org.junit.internal.runners.model.ReflectiveCallable.run() line: 12
]
}
Virtual Thread is pinned
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 6.215 s <<< FAILURE! - in com.example.general.JFRTest
[ERROR] com.example.general.JFRTest.pinnedVirtualThreads Time elapsed: 6.187 s <<< FAILURE!
java.lang.AssertionError: jdk.VirtualThreadPinned was not sent
Как видите, эта часть должна вызвать событие jdk.VirtualThreadPinned, но этого не происходит. С другой стороны, ловится другое событие StartEvent:
Thread vt = Thread.ofVirtual().start(() -> {
synchronized (this) {
System.out.println("Virtual Thread is pinned");
}
});
Я не знаю, почему.
Это моя версия JDK: 21.0.3+7-LTS-152.




jdk.VirtualThreadPinned запускается только тогда, когда среда выполнения пытается припарковать закрепленный поток. Вы можете вызвать это, например, вызвав Thread.sleep с запущенным виртуальным потоком:
Thread vt = Thread.ofVirtual().start(() -> {
synchronized (lock) {
System.out.println("Virtual Thread is pinned");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Вам также необходимо убедиться, что поток событий сброшен, иначе ваш обратный вызов для события jdk.VirtualThreadPinned никогда не будет вызван. Вы можете сделать это, вызвав .stop() в потоке записи (close()-включение потока резко останавливает запись и не ждет, пока будут обработаны все события). Например, прямо перед концом блока try:
vt.join();
recordingStream.stop();
} // end of try
С этими двумя изменениями я вижу, что событие запускается:
VirtualThreadPinned event detected!
Timestamp: jdk.VirtualThreadPinned {
startTime = 10:28:32.075 (2024-06-12)
duration = 998 ms
eventThread = "" (javaThreadId = 37, virtual)
stackTrace = [
java.lang.VirtualThread.parkOnCarrierThread(boolean, long) line: 689
java.lang.VirtualThread.parkNanos(long) line: 648
java.lang.VirtualThread.sleepNanos(long) line: 812
java.lang.Thread.sleepNanos(long) line: 489
java.lang.Thread.sleep(long) line: 522
...
]
}
Другой вопрос, который у меня возникает: как определить в трассировке стека, где находится монитор?
Я вижу, вы как-то связаны с этой темой. У меня есть еще один вопрос: почему трассировка стека обрезается до 5 строк? См. jdk.jfr.consumer.RecordedObject#toString. Наличие полного стека на самом деле является целью понять, почему поток закреплен.