From 43b82b1cf3e9a65267f5e3af1183e3d568de3ced Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 21 Aug 2024 15:17:20 -0400 Subject: [PATCH 1/5] lazy vthread registration --- .../svm/core/jfr/JfrThreadRepository.java | 5 ----- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 22 +++++++++++++++++++ .../jfr/events/JavaMonitorEnterEvent.java | 8 +++---- .../svm/core/jfr/traceid/JfrTraceIdEpoch.java | 7 ++++++ .../oracle/svm/core/monitor/JavaMonitor.java | 9 ++++---- .../monitor/MultiThreadedMonitorSupport.java | 2 +- .../Target_java_lang_VirtualThread.java | 9 ++++---- 7 files changed, 42 insertions(+), 20 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index fa8b9ffc7ff0..a7670399c93e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -92,11 +92,6 @@ public void registerRunningThreads() { Thread thread = PlatformThreads.fromVMThread(isolateThread); if (thread != null) { registerThread(thread); - // Re-register vthreads that are already mounted. - Thread vthread = PlatformThreads.getMountedVirtualThread(thread); - if (vthread != null) { - registerThread(vthread); - } } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java index eff3527c0480..ddbcbff7c5fd 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java @@ -26,6 +26,7 @@ import java.util.List; +import com.oracle.svm.core.SubstrateUtil; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; @@ -42,6 +43,7 @@ import com.oracle.svm.core.jfr.oldobject.JfrOldObjectRepository; import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler; import com.oracle.svm.core.jfr.throttling.JfrEventThrottling; +import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.sampler.SamplerBufferPool; import com.oracle.svm.core.sampler.SamplerBuffersAccess; @@ -49,6 +51,7 @@ import com.oracle.svm.core.sampler.SubstrateSigprofHandler; import com.oracle.svm.core.thread.JavaThreads; import com.oracle.svm.core.thread.JavaVMOperation; +import com.oracle.svm.core.thread.Target_java_lang_VirtualThread; import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.util.VMError; @@ -303,6 +306,7 @@ public long getStackTraceId(JfrEvent eventType, int skipCount) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static long getThreadId(Thread thread) { if (HasJfrSupport.get()) { + maybeRegisterVirtualThread(thread); return JavaThreads.getThreadId(thread); } return 0; @@ -311,11 +315,29 @@ public static long getThreadId(Thread thread) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static long getCurrentThreadId() { if (HasJfrSupport.get()) { + maybeRegisterVirtualThread(Thread.currentThread()); return JavaThreads.getCurrentThreadId(); } return 0; } + /** + * Register virtual threads if they aren't registered already. Platform threads are registered + * eagerly when started and at chunk rotations. + */ + @Uninterruptible(reason = "Epoch should not change while checking generation.") + private static void maybeRegisterVirtualThread(Thread thread) { + // Do quick preliminary checks to avoid global locking unless necessary. + if (JavaThreads.isVirtual(thread)) { + Target_java_lang_VirtualThread tjlv = SubstrateUtil.cast(thread, Target_java_lang_VirtualThread.class); + int currentEpochGen = JfrTraceIdEpoch.getInstance().currentEpochGeneration(); + if (tjlv.jfrGeneration != currentEpochGen) { + getThreadRepo().registerThread(thread); + tjlv.jfrGeneration = currentEpochGen; + } + } + } + /** * See {@link JVM#storeMetadataDescriptor}. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java index 671924ba1a48..b32c64b056df 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java @@ -39,14 +39,14 @@ import com.oracle.svm.core.jfr.SubstrateJVM; public class JavaMonitorEnterEvent { - public static void emit(Object obj, long previousOwnerTid, long startTicks) { + public static void emit(Object obj, Thread previousOwner, long startTicks) { if (HasJfrSupport.get()) { - emit0(obj, previousOwnerTid, startTicks); + emit0(obj, previousOwner, startTicks); } } @Uninterruptible(reason = "Accesses a JFR buffer.") - public static void emit0(Object obj, long previousOwnerTid, long startTicks) { + public static void emit0(Object obj, Thread previousOwner, long startTicks) { long duration = JfrTicks.duration(startTicks); if (JfrEvent.JavaMonitorEnter.shouldEmit(duration)) { JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); @@ -58,7 +58,7 @@ public static void emit0(Object obj, long previousOwnerTid, long startTicks) { JfrNativeEventWriter.putEventThread(data); JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorEnter, 0)); JfrNativeEventWriter.putClass(data, obj.getClass()); - JfrNativeEventWriter.putLong(data, previousOwnerTid); + JfrNativeEventWriter.putThread(data, previousOwner); JfrNativeEventWriter.putLong(data, Word.objectToUntrackedPointer(obj).rawValue()); JfrNativeEventWriter.endSmallEvent(data); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java index 400d919c4973..c6437f2551aa 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java @@ -43,6 +43,7 @@ public class JfrTraceIdEpoch { private static final long EPOCH_1_BIT = 0b10; private boolean epoch; + private int epochGeneration; @Fold public static JfrTraceIdEpoch getInstance() { @@ -57,6 +58,7 @@ public JfrTraceIdEpoch() { public void changeEpoch() { assert VMOperation.isInProgressAtSafepoint(); epoch = !epoch; + epochGeneration++; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -74,6 +76,11 @@ public boolean currentEpoch() { return epoch; } + @Uninterruptible(reason = "Avoid epoch changing while checking generation.", callerMustBe = true) + public int currentEpochGeneration() { + return epochGeneration; + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean previousEpoch() { return !epoch; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java index f0d580123079..26882730c6da 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java @@ -36,7 +36,6 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jfr.JfrTicks; -import com.oracle.svm.core.jfr.SubstrateJVM; import com.oracle.svm.core.jfr.events.JavaMonitorEnterEvent; import com.oracle.svm.core.thread.JavaThreads; import com.oracle.svm.core.util.BasedOnJDKClass; @@ -61,20 +60,20 @@ @BasedOnJDKClass(ReentrantLock.class) @BasedOnJDKClass(value = ReentrantLock.class, innerClass = "Sync") public class JavaMonitor extends JavaMonitorQueuedSynchronizer { - protected long latestJfrTid; + protected Thread latest; public JavaMonitor() { - latestJfrTid = 0; + latest = null; } public void monitorEnter(Object obj) { if (!tryLock()) { long startTicks = JfrTicks.elapsedTicks(); acquire(1); - JavaMonitorEnterEvent.emit(obj, latestJfrTid, startTicks); + JavaMonitorEnterEvent.emit(obj, latest, startTicks); } - latestJfrTid = SubstrateJVM.getCurrentThreadId(); + latest = Thread.currentThread(); } public void monitorExit() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MultiThreadedMonitorSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MultiThreadedMonitorSupport.java index 7a458f0a0c65..c61ba67c87a2 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MultiThreadedMonitorSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MultiThreadedMonitorSupport.java @@ -260,7 +260,7 @@ public void monitorEnter(Object obj, MonitorInflationCause cause) { monitor = (JavaMonitor) UNSAFE.compareAndExchangeReference(obj, monitorOffset, null, newMonitor); if (monitor == null) { // successful JavaMonitorInflateEvent.emit(obj, startTicks, MonitorInflationCause.MONITOR_ENTER); - newMonitor.latestJfrTid = current; + newMonitor.latest = Thread.currentThread(); return; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_VirtualThread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_VirtualThread.java index de0fab47147a..c3f080632602 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_VirtualThread.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_VirtualThread.java @@ -47,8 +47,6 @@ import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.jdk.JDK21OrEarlier; import com.oracle.svm.core.jdk.JDKLatest; -import com.oracle.svm.core.jfr.HasJfrSupport; -import com.oracle.svm.core.jfr.SubstrateJVM; import com.oracle.svm.core.monitor.MonitorInflationCause; import com.oracle.svm.core.monitor.MonitorSupport; import com.oracle.svm.core.util.VMError; @@ -113,6 +111,10 @@ public final class Target_java_lang_VirtualThread { private Executor nondefaultScheduler; // Checkstyle: resume + @Inject // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // + public volatile int jfrGeneration = -1; + @Alias private static native ForkJoinPool createDefaultScheduler(); @@ -330,9 +332,6 @@ void mount() { } carrier.setCurrentThread(asThread(this)); - if (HasJfrSupport.get()) { - SubstrateJVM.getThreadRepo().registerThread(asThread(this)); - } } @Substitute // not needed on newer JDKs that use safe disableSuspendAndPreempt() From e33f0ac5f6bd58064be2fd19b1f467dd5f98810f Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 4 Sep 2024 11:07:14 -0400 Subject: [PATCH 2/5] remove epoch boolean --- .../svm/core/jfr/traceid/JfrTraceIdEpoch.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java index c6437f2551aa..8efcaab0532b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java @@ -42,7 +42,6 @@ public class JfrTraceIdEpoch { private static final long EPOCH_0_BIT = 0b01; private static final long EPOCH_1_BIT = 0b10; - private boolean epoch; private int epochGeneration; @Fold @@ -53,27 +52,29 @@ public static JfrTraceIdEpoch getInstance() { @Platforms(Platform.HOSTED_ONLY.class) public JfrTraceIdEpoch() { } - + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + private boolean getEpoch() { + return (epochGeneration & 1) == 0; + } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void changeEpoch() { assert VMOperation.isInProgressAtSafepoint(); - epoch = !epoch; epochGeneration++; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) long thisEpochBit() { - return epoch ? EPOCH_1_BIT : EPOCH_0_BIT; + return getEpoch() ? EPOCH_1_BIT : EPOCH_0_BIT; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) long previousEpochBit() { - return epoch ? EPOCH_0_BIT : EPOCH_1_BIT; + return getEpoch() ? EPOCH_0_BIT : EPOCH_1_BIT; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean currentEpoch() { - return epoch; + return getEpoch(); } @Uninterruptible(reason = "Avoid epoch changing while checking generation.", callerMustBe = true) @@ -83,6 +84,6 @@ public int currentEpochGeneration() { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean previousEpoch() { - return !epoch; + return !getEpoch(); } } From c1e4ab092f58f328c5a5eb46ad36fcb3ecddf468 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 4 Sep 2024 11:16:55 -0400 Subject: [PATCH 3/5] style --- .../src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java index 8efcaab0532b..b45a67e60c2f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java @@ -52,10 +52,12 @@ public static JfrTraceIdEpoch getInstance() { @Platforms(Platform.HOSTED_ONLY.class) public JfrTraceIdEpoch() { } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) private boolean getEpoch() { return (epochGeneration & 1) == 0; } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void changeEpoch() { assert VMOperation.isInProgressAtSafepoint(); From 22add5c2f8c815c9109faa6fba8649a847b749a5 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Fri, 13 Sep 2024 12:27:37 -0400 Subject: [PATCH 4/5] Use putThread to avoid races. --- .../svm/core/jfr/JfrNativeEventWriter.java | 2 ++ .../svm/core/jfr/JfrThreadRepository.java | 27 ++++++++++++++++--- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 13 ++++++--- .../core/jfr/events/ExecutionSampleEvent.java | 2 +- .../core/jfr/events/JavaMonitorWaitEvent.java | 6 ++--- .../svm/core/jfr/events/ThreadStartEvent.java | 2 +- .../JavaMonitorQueuedSynchronizer.java | 13 +++++---- 7 files changed, 46 insertions(+), 19 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java index 9c6da80d0ee7..3f12858073b7 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java @@ -250,12 +250,14 @@ public static void putThread(JfrNativeEventWriterData data, Thread thread) { if (thread == null) { putThread(data, 0L); } else { + SubstrateJVM.maybeRegisterVirtualThread(thread); putThread(data, SubstrateJVM.getThreadId(thread)); } } @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true) public static void putThread(JfrNativeEventWriterData data, long threadId) { + SubstrateJVM.maybeRegisterVirtualThread(threadId); putLong(data, threadId); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index a7670399c93e..0f0e033e98ab 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.core.jfr; +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -96,6 +98,19 @@ public void registerRunningThreads() { } } + /** + * If this method is called on platform threads, nothing will happen since they are already + * registered eagerly upon starting and at epoch changes. + */ + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") + public void registerThread(long threadId) { + if (!SubstrateJVM.get().isRecording()) { + return; + } + + registerThread0(threadId, 0, true, null, null); + } + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") public void registerThread(Thread thread) { if (!SubstrateJVM.get().isRecording()) { @@ -103,7 +118,13 @@ public void registerThread(Thread thread) { } long threadId = JavaThreads.getThreadId(thread); + boolean isVirtual = JavaThreads.isVirtual(thread); + long osThreadId = isVirtual ? 0 : threadId; + registerThread0(threadId, osThreadId, isVirtual, thread, thread.getName()); + } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) + private void registerThread0(long threadId, long osThreadId, boolean isVirtual, Thread thread, String name) { JfrVisited visitedThread = StackValue.get(JfrVisited.class); visitedThread.setId(threadId); visitedThread.setHash(UninterruptibleUtils.Long.hashCode(threadId)); @@ -124,14 +145,12 @@ public void registerThread(Thread thread) { JfrNativeEventWriterDataAccess.initialize(data, epochData.threadBuffer); /* Similar to JfrThreadConstant::serialize in HotSpot. */ - boolean isVirtual = JavaThreads.isVirtual(thread); - long osThreadId = isVirtual ? 0 : threadId; long threadGroupId = registerThreadGroup(thread, isVirtual); JfrNativeEventWriter.putLong(data, threadId); - JfrNativeEventWriter.putString(data, thread.getName()); // OS thread name + JfrNativeEventWriter.putString(data, name); // OS thread name JfrNativeEventWriter.putLong(data, osThreadId); // OS thread id - JfrNativeEventWriter.putString(data, thread.getName()); // Java thread name + JfrNativeEventWriter.putString(data, name); // Java thread name JfrNativeEventWriter.putLong(data, threadId); // Java thread id JfrNativeEventWriter.putLong(data, threadGroupId); // Java thread group JfrNativeEventWriter.putBoolean(data, isVirtual); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java index ddbcbff7c5fd..b678420bc817 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java @@ -306,7 +306,6 @@ public long getStackTraceId(JfrEvent eventType, int skipCount) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static long getThreadId(Thread thread) { if (HasJfrSupport.get()) { - maybeRegisterVirtualThread(thread); return JavaThreads.getThreadId(thread); } return 0; @@ -315,7 +314,6 @@ public static long getThreadId(Thread thread) { @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static long getCurrentThreadId() { if (HasJfrSupport.get()) { - maybeRegisterVirtualThread(Thread.currentThread()); return JavaThreads.getCurrentThreadId(); } return 0; @@ -326,7 +324,7 @@ public static long getCurrentThreadId() { * eagerly when started and at chunk rotations. */ @Uninterruptible(reason = "Epoch should not change while checking generation.") - private static void maybeRegisterVirtualThread(Thread thread) { + public static void maybeRegisterVirtualThread(Thread thread) { // Do quick preliminary checks to avoid global locking unless necessary. if (JavaThreads.isVirtual(thread)) { Target_java_lang_VirtualThread tjlv = SubstrateUtil.cast(thread, Target_java_lang_VirtualThread.class); @@ -338,6 +336,15 @@ private static void maybeRegisterVirtualThread(Thread thread) { } } + /** + * {@link SubstrateJVM#maybeRegisterVirtualThread(Thread)} Is preferred over this method since + * it can perform preliminary checks to avoid locking the Thread Repository unnecessarily. + */ + @Uninterruptible(reason = "Epoch should not change while checking generation.") + public static void maybeRegisterVirtualThread(long tid) { + getThreadRepo().registerThread(tid); + } + /** * See {@link JVM#storeMetadataDescriptor}. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecutionSampleEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecutionSampleEvent.java index 1a6723b9a532..0a14b381b7af 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecutionSampleEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ExecutionSampleEvent.java @@ -41,7 +41,7 @@ public static void writeExecutionSample(long elapsedTicks, long threadId, long s JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.ExecutionSample); JfrNativeEventWriter.putLong(data, elapsedTicks); - JfrNativeEventWriter.putLong(data, threadId); + JfrNativeEventWriter.putThread(data, threadId); JfrNativeEventWriter.putLong(data, stackTraceId); JfrNativeEventWriter.putLong(data, threadState); JfrNativeEventWriter.endSmallEvent(data); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java index 27322780e18a..85ab907e2d30 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java @@ -40,7 +40,7 @@ import jdk.graal.compiler.word.Word; public class JavaMonitorWaitEvent { - public static void emit(long startTicks, Object obj, long notifier, long timeout, boolean timedOut) { + public static void emit(long startTicks, Object obj, Thread notifier, long timeout, boolean timedOut) { if (HasJfrSupport.get() && obj != null && !Target_jdk_jfr_internal_JVM_ChunkRotationMonitor.class.equals(obj.getClass()) && !Target_jdk_jfr_internal_management_HiddenWait.class.equals(obj.getClass())) { emit0(startTicks, obj, notifier, timeout, timedOut); @@ -48,7 +48,7 @@ public static void emit(long startTicks, Object obj, long notifier, long timeout } @Uninterruptible(reason = "Accesses a JFR buffer.") - private static void emit0(long startTicks, Object obj, long notifier, long timeout, boolean timedOut) { + private static void emit0(long startTicks, Object obj, Thread notifier, long timeout, boolean timedOut) { long duration = JfrTicks.duration(startTicks); if (JfrEvent.JavaMonitorWait.shouldEmit(duration)) { JfrNativeEventWriterData data = org.graalvm.nativeimage.StackValue.get(JfrNativeEventWriterData.class); @@ -60,7 +60,7 @@ private static void emit0(long startTicks, Object obj, long notifier, long timeo JfrNativeEventWriter.putEventThread(data); JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorWait, 0)); JfrNativeEventWriter.putClass(data, obj.getClass()); - JfrNativeEventWriter.putLong(data, notifier); + JfrNativeEventWriter.putThread(data, notifier); JfrNativeEventWriter.putLong(data, timeout); JfrNativeEventWriter.putBoolean(data, timedOut); JfrNativeEventWriter.putLong(data, Word.objectToUntrackedPointer(obj).rawValue()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadStartEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadStartEvent.java index 1eb36418442e..e6965cba15e1 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadStartEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/ThreadStartEvent.java @@ -47,7 +47,7 @@ public static void emit(Thread thread) { JfrNativeEventWriter.putEventThread(data); JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.ThreadStart, 0)); JfrNativeEventWriter.putThread(data, thread); - JfrNativeEventWriter.putLong(data, JavaThreads.getParentThreadId(thread)); + JfrNativeEventWriter.putThread(data, JavaThreads.getParentThreadId(thread)); JfrNativeEventWriter.endSmallEvent(data); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java index c20e2fc2b058..9dd7b0a55def 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java @@ -32,7 +32,6 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jfr.JfrTicks; -import com.oracle.svm.core.jfr.SubstrateJVM; import com.oracle.svm.core.jfr.events.JavaMonitorWaitEvent; import com.oracle.svm.core.thread.JavaThreads; import com.oracle.svm.core.util.BasedOnJDKClass; @@ -120,7 +119,7 @@ static final class ExclusiveNode extends Node { @BasedOnJDKClass(value = AbstractQueuedLongSynchronizer.class, innerClass = "ConditionNode") static final class ConditionNode extends Node { ConditionNode nextWaiter; - long notifierJfrTid; + Thread notifierJfr; // see AbstractQueuedLongSynchronizer.ConditionNode.isReleasable() public boolean isReleasable() { @@ -457,7 +456,7 @@ private void doSignal(ConditionNode first, boolean all) { first.nextWaiter = null; // GC assistance } if ((first.getAndUnsetStatus(COND) & COND) != 0) { - first.notifierJfrTid = SubstrateJVM.getCurrentThreadId(); + first.notifierJfr = Thread.currentThread(); enqueue(first); if (!all) { break; @@ -564,7 +563,7 @@ private ConditionNode newConditionNode() { public void await(Object obj) throws InterruptedException { long startTicks = JfrTicks.elapsedTicks(); if (Thread.interrupted()) { - JavaMonitorWaitEvent.emit(startTicks, obj, 0, 0L, false); + JavaMonitorWaitEvent.emit(startTicks, obj, null, 0L, false); throw new InterruptedException(); } ConditionNode node = newConditionNode(); @@ -587,7 +586,7 @@ public void await(Object obj) throws InterruptedException { } node.clearStatus(); // waiting is done, emit wait event - JavaMonitorWaitEvent.emit(startTicks, obj, node.notifierJfrTid, 0L, false); + JavaMonitorWaitEvent.emit(startTicks, obj, node.notifierJfr, 0L, false); acquire(node, savedAcquisitions); if (interrupted) { if (cancelled) { @@ -604,7 +603,7 @@ public boolean await(Object obj, long time, TimeUnit unit) throws InterruptedExc long startTicks = JfrTicks.elapsedTicks(); long nanosTimeout = unit.toNanos(time); if (Thread.interrupted()) { - JavaMonitorWaitEvent.emit(startTicks, obj, 0, 0L, false); + JavaMonitorWaitEvent.emit(startTicks, obj, null, 0L, false); throw new InterruptedException(); } ConditionNode node = newConditionNode(); @@ -627,7 +626,7 @@ public boolean await(Object obj, long time, TimeUnit unit) throws InterruptedExc } node.clearStatus(); // waiting is done, emit wait event - JavaMonitorWaitEvent.emit(startTicks, obj, node.notifierJfrTid, time, cancelled); + JavaMonitorWaitEvent.emit(startTicks, obj, node.notifierJfr, time, cancelled); acquire(node, savedAcquisitions); if (cancelled) { unlinkCancelledWaiters(node); From 54c5223d8938ffec1b649d3c89462dc64aeb9a51 Mon Sep 17 00:00:00 2001 From: Christian Haeubl Date: Wed, 23 Apr 2025 12:46:46 +0200 Subject: [PATCH 5/5] Skip unnecessary JFR registrations for virtual threads. --- .../svm/core/jfr/JfrNativeEventWriter.java | 5 +- .../svm/core/jfr/JfrThreadRepository.java | 54 +++++++++++-------- .../com/oracle/svm/core/jfr/SubstrateJVM.java | 29 ---------- .../jfr/events/JavaMonitorEnterEvent.java | 11 ++-- .../core/jfr/events/JavaMonitorWaitEvent.java | 8 +-- .../svm/core/jfr/traceid/JfrTraceIdEpoch.java | 27 +++++----- .../oracle/svm/core/monitor/JavaMonitor.java | 9 ++-- .../JavaMonitorQueuedSynchronizer.java | 13 ++--- .../monitor/MultiThreadedMonitorSupport.java | 2 +- .../oracle/svm/core/thread/JavaThreads.java | 5 +- .../Target_java_lang_VirtualThread.java | 16 +++++- 11 files changed, 90 insertions(+), 89 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java index 81aefcafe80d..0e77cd0f0e18 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.jfr; -import jdk.graal.compiler.word.Word; import org.graalvm.word.Pointer; import org.graalvm.word.UnsignedWord; @@ -35,6 +34,8 @@ import com.oracle.svm.core.util.DuplicatedInNativeCode; import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.word.Word; + /** * A JFR event writer that does not allocate any objects in the Java heap. Can only be used from * {@link Uninterruptible} code to prevent races between threads that try to write a native JFR @@ -250,14 +251,12 @@ public static void putThread(JfrNativeEventWriterData data, Thread thread) { if (thread == null) { putThread(data, 0L); } else { - SubstrateJVM.maybeRegisterVirtualThread(thread); putThread(data, SubstrateJVM.getThreadId(thread)); } } @Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true) public static void putThread(JfrNativeEventWriterData data, long threadId) { - SubstrateJVM.maybeRegisterVirtualThread(threadId); putLong(data, threadId); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java index fe3d6ec68348..59bc7d635fe8 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java @@ -24,9 +24,6 @@ */ package com.oracle.svm.core.jfr; -import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; -import jdk.graal.compiler.word.Word; - import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -42,9 +39,12 @@ import com.oracle.svm.core.thread.JavaThreads; import com.oracle.svm.core.thread.PlatformThreads; import com.oracle.svm.core.thread.Target_java_lang_Thread; +import com.oracle.svm.core.thread.Target_java_lang_VirtualThread; import com.oracle.svm.core.thread.VMOperation; import com.oracle.svm.core.thread.VMThreads; +import jdk.graal.compiler.word.Word; + /** * Repository that collects all metadata about threads and thread groups. * @@ -94,37 +94,32 @@ public void registerRunningThreads() { Thread thread = PlatformThreads.fromVMThread(isolateThread); if (thread != null) { registerThread(thread); + // Re-register vthreads that are already mounted. + Thread vthread = PlatformThreads.getMountedVirtualThread(thread); + if (vthread != null) { + registerThread(vthread); + } } } } - /** - * If this method is called on platform threads, nothing will happen since they are already - * registered eagerly upon starting and at epoch changes. - */ - @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") - public void registerThread(long threadId) { + @Uninterruptible(reason = "Prevent epoch changes. Prevent races with VM operations that start/stop recording.") + public void registerThread(Thread thread) { if (!SubstrateJVM.get().isRecording()) { return; } - registerThread0(threadId, 0, true, null, null); - } - - @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") - public void registerThread(Thread thread) { - if (!SubstrateJVM.get().isRecording()) { + boolean isVirtual = JavaThreads.isVirtual(thread); + if (isVirtual && isVirtualThreadAlreadyRegistered(thread)) { return; } - long threadId = JavaThreads.getThreadId(thread); - boolean isVirtual = JavaThreads.isVirtual(thread); - long osThreadId = isVirtual ? 0 : threadId; - registerThread0(threadId, osThreadId, isVirtual, thread, thread.getName()); + registerThread0(thread, isVirtual); } - @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) - private void registerThread0(long threadId, long osThreadId, boolean isVirtual, Thread thread, String name) { + @Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.") + private void registerThread0(Thread thread, boolean isVirtual) { + long threadId = JavaThreads.getThreadId(thread); JfrVisited visitedThread = StackValue.get(JfrVisited.class); visitedThread.setId(threadId); visitedThread.setHash(UninterruptibleUtils.Long.hashCode(threadId)); @@ -145,7 +140,9 @@ private void registerThread0(long threadId, long osThreadId, boolean isVirtual, JfrNativeEventWriterDataAccess.initialize(data, epochData.threadBuffer); /* Similar to JfrThreadConstant::serialize in HotSpot. */ + long osThreadId = isVirtual ? 0 : threadId; long threadGroupId = registerThreadGroup(thread, isVirtual); + String name = thread.getName(); JfrNativeEventWriter.putLong(data, threadId); JfrNativeEventWriter.putString(data, name); // OS thread name @@ -158,6 +155,11 @@ private void registerThread0(long threadId, long osThreadId, boolean isVirtual, return; } + if (isVirtual) { + Target_java_lang_VirtualThread vthread = JavaThreads.toVirtualTarget(thread); + vthread.jfrEpochId = JfrTraceIdEpoch.getInstance().currentEpochId(); + } + epochData.unflushedThreadCount++; /* The buffer may have been replaced with a new one. */ epochData.threadBuffer = data.getJfrBuffer(); @@ -166,6 +168,16 @@ private void registerThread0(long threadId, long osThreadId, boolean isVirtual, } } + @Uninterruptible(reason = "Epoch must not change while in this method.", callerMustBe = true) + private static boolean isVirtualThreadAlreadyRegistered(Thread thread) { + assert JavaThreads.isVirtual(thread); + + /* Threads only need to be registered once per epoch. */ + Target_java_lang_VirtualThread vthread = JavaThreads.toVirtualTarget(thread); + long epochId = JfrTraceIdEpoch.getInstance().currentEpochId(); + return vthread.jfrEpochId == epochId; + } + @Uninterruptible(reason = "Epoch must not change while in this method.") private long registerThreadGroup(Thread thread, boolean isVirtual) { if (isVirtual) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java index ab8e3d861ae2..0f7eb7fab89f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java @@ -26,7 +26,6 @@ import java.util.List; -import com.oracle.svm.core.SubstrateUtil; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.IsolateThread; import org.graalvm.nativeimage.Platform; @@ -42,7 +41,6 @@ import com.oracle.svm.core.jfr.oldobject.JfrOldObjectRepository; import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler; import com.oracle.svm.core.jfr.throttling.JfrEventThrottling; -import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch; import com.oracle.svm.core.log.Log; import com.oracle.svm.core.sampler.SamplerBufferPool; import com.oracle.svm.core.sampler.SamplerBuffersAccess; @@ -50,7 +48,6 @@ import com.oracle.svm.core.sampler.SubstrateSigprofHandler; import com.oracle.svm.core.thread.JavaThreads; import com.oracle.svm.core.thread.JavaVMOperation; -import com.oracle.svm.core.thread.Target_java_lang_VirtualThread; import com.oracle.svm.core.thread.VMThreads; import com.oracle.svm.core.util.VMError; @@ -320,32 +317,6 @@ public static long getCurrentThreadId() { return 0; } - /** - * Register virtual threads if they aren't registered already. Platform threads are registered - * eagerly when started and at chunk rotations. - */ - @Uninterruptible(reason = "Epoch should not change while checking generation.") - public static void maybeRegisterVirtualThread(Thread thread) { - // Do quick preliminary checks to avoid global locking unless necessary. - if (JavaThreads.isVirtual(thread)) { - Target_java_lang_VirtualThread tjlv = SubstrateUtil.cast(thread, Target_java_lang_VirtualThread.class); - int currentEpochGen = JfrTraceIdEpoch.getInstance().currentEpochGeneration(); - if (tjlv.jfrGeneration != currentEpochGen) { - getThreadRepo().registerThread(thread); - tjlv.jfrGeneration = currentEpochGen; - } - } - } - - /** - * {@link SubstrateJVM#maybeRegisterVirtualThread(Thread)} Is preferred over this method since - * it can perform preliminary checks to avoid locking the Thread Repository unnecessarily. - */ - @Uninterruptible(reason = "Epoch should not change while checking generation.") - public static void maybeRegisterVirtualThread(long tid) { - getThreadRepo().registerThread(tid); - } - /** * See {@link JVM#storeMetadataDescriptor}. */ diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java index b32c64b056df..1d72ef3af785 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java @@ -26,7 +26,6 @@ package com.oracle.svm.core.jfr.events; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.StackValue; import com.oracle.svm.core.Uninterruptible; @@ -38,15 +37,17 @@ import com.oracle.svm.core.jfr.JfrTicks; import com.oracle.svm.core.jfr.SubstrateJVM; +import jdk.graal.compiler.word.Word; + public class JavaMonitorEnterEvent { - public static void emit(Object obj, Thread previousOwner, long startTicks) { + public static void emit(Object obj, long previousOwnerTid, long startTicks) { if (HasJfrSupport.get()) { - emit0(obj, previousOwner, startTicks); + emit0(obj, previousOwnerTid, startTicks); } } @Uninterruptible(reason = "Accesses a JFR buffer.") - public static void emit0(Object obj, Thread previousOwner, long startTicks) { + public static void emit0(Object obj, long previousOwnerTid, long startTicks) { long duration = JfrTicks.duration(startTicks); if (JfrEvent.JavaMonitorEnter.shouldEmit(duration)) { JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); @@ -58,7 +59,7 @@ public static void emit0(Object obj, Thread previousOwner, long startTicks) { JfrNativeEventWriter.putEventThread(data); JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorEnter, 0)); JfrNativeEventWriter.putClass(data, obj.getClass()); - JfrNativeEventWriter.putThread(data, previousOwner); + JfrNativeEventWriter.putThread(data, previousOwnerTid); JfrNativeEventWriter.putLong(data, Word.objectToUntrackedPointer(obj).rawValue()); JfrNativeEventWriter.endSmallEvent(data); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java index 85ab907e2d30..3c474ab4ba70 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java @@ -40,15 +40,15 @@ import jdk.graal.compiler.word.Word; public class JavaMonitorWaitEvent { - public static void emit(long startTicks, Object obj, Thread notifier, long timeout, boolean timedOut) { + public static void emit(long startTicks, Object obj, long notifierTid, long timeout, boolean timedOut) { if (HasJfrSupport.get() && obj != null && !Target_jdk_jfr_internal_JVM_ChunkRotationMonitor.class.equals(obj.getClass()) && !Target_jdk_jfr_internal_management_HiddenWait.class.equals(obj.getClass())) { - emit0(startTicks, obj, notifier, timeout, timedOut); + emit0(startTicks, obj, notifierTid, timeout, timedOut); } } @Uninterruptible(reason = "Accesses a JFR buffer.") - private static void emit0(long startTicks, Object obj, Thread notifier, long timeout, boolean timedOut) { + private static void emit0(long startTicks, Object obj, long notifierTid, long timeout, boolean timedOut) { long duration = JfrTicks.duration(startTicks); if (JfrEvent.JavaMonitorWait.shouldEmit(duration)) { JfrNativeEventWriterData data = org.graalvm.nativeimage.StackValue.get(JfrNativeEventWriterData.class); @@ -60,7 +60,7 @@ private static void emit0(long startTicks, Object obj, Thread notifier, long tim JfrNativeEventWriter.putEventThread(data); JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorWait, 0)); JfrNativeEventWriter.putClass(data, obj.getClass()); - JfrNativeEventWriter.putThread(data, notifier); + JfrNativeEventWriter.putThread(data, notifierTid); JfrNativeEventWriter.putLong(data, timeout); JfrNativeEventWriter.putBoolean(data, timedOut); JfrNativeEventWriter.putLong(data, Word.objectToUntrackedPointer(obj).rawValue()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java index b45a67e60c2f..b0ce23c7f153 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java @@ -25,7 +25,6 @@ package com.oracle.svm.core.jfr.traceid; -import jdk.graal.compiler.api.replacements.Fold; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -33,6 +32,8 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.thread.VMOperation; +import jdk.graal.compiler.api.replacements.Fold; + /** * Class holding the current JFR epoch. JFR uses an epoch system to safely separate constant pool * entries between adjacent chunks. Used to get the current or previous epoch and switch from one @@ -42,7 +43,7 @@ public class JfrTraceIdEpoch { private static final long EPOCH_0_BIT = 0b01; private static final long EPOCH_1_BIT = 0b10; - private int epochGeneration; + private long epochId; @Fold public static JfrTraceIdEpoch getInstance() { @@ -53,15 +54,10 @@ public static JfrTraceIdEpoch getInstance() { public JfrTraceIdEpoch() { } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) - private boolean getEpoch() { - return (epochGeneration & 1) == 0; - } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public void changeEpoch() { assert VMOperation.isInProgressAtSafepoint(); - epochGeneration++; + epochId++; } @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) @@ -79,13 +75,18 @@ public boolean currentEpoch() { return getEpoch(); } - @Uninterruptible(reason = "Avoid epoch changing while checking generation.", callerMustBe = true) - public int currentEpochGeneration() { - return epochGeneration; - } - @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public boolean previousEpoch() { return !getEpoch(); } + + @Uninterruptible(reason = "Prevent epoch from changing.", callerMustBe = true) + public long currentEpochId() { + return epochId; + } + + @Uninterruptible(reason = "Prevent epoch from changing.", callerMustBe = true) + private boolean getEpoch() { + return (epochId & 1) == 0; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java index 1819a035de2c..8461d24bc788 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java @@ -36,6 +36,7 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jfr.JfrTicks; +import com.oracle.svm.core.jfr.SubstrateJVM; import com.oracle.svm.core.jfr.events.JavaMonitorEnterEvent; import com.oracle.svm.core.thread.JavaThreads; import com.oracle.svm.core.util.BasedOnJDKClass; @@ -60,20 +61,20 @@ @BasedOnJDKClass(ReentrantLock.class) @BasedOnJDKClass(value = ReentrantLock.class, innerClass = "Sync") public class JavaMonitor extends JavaMonitorQueuedSynchronizer { - protected Thread latest; + protected long latestJfrTid; public JavaMonitor() { - latest = null; + latestJfrTid = 0; } public void monitorEnter(Object obj) { if (!tryLock()) { long startTicks = JfrTicks.elapsedTicks(); acquire(1); - JavaMonitorEnterEvent.emit(obj, latest, startTicks); + JavaMonitorEnterEvent.emit(obj, latestJfrTid, startTicks); } - latest = Thread.currentThread(); + latestJfrTid = SubstrateJVM.getCurrentThreadId(); } public void monitorExit() { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java index 03d632b7efda..3ca87f848b20 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitorQueuedSynchronizer.java @@ -32,6 +32,7 @@ import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.jfr.JfrTicks; +import com.oracle.svm.core.jfr.SubstrateJVM; import com.oracle.svm.core.jfr.events.JavaMonitorWaitEvent; import com.oracle.svm.core.thread.JavaThreads; import com.oracle.svm.core.util.BasedOnJDKClass; @@ -120,7 +121,7 @@ static final class ExclusiveNode extends Node { @BasedOnJDKClass(value = AbstractQueuedLongSynchronizer.class, innerClass = "ConditionNode") static final class ConditionNode extends Node { ConditionNode nextWaiter; // link to next waiting node - Thread notifierJfr; + long notifierJfrTid; // see AbstractQueuedLongSynchronizer.ConditionNode.isReleasable() public boolean isReleasable() { @@ -519,7 +520,7 @@ private void doSignal(ConditionNode first, boolean all) { } if ((first.getAndUnsetStatus(COND) & COND) != 0) { /* JFR-related code is SVM-specific. */ - first.notifierJfr = Thread.currentThread(); + first.notifierJfrTid = SubstrateJVM.getCurrentThreadId(); enqueue(first); if (!all) { break; @@ -628,7 +629,7 @@ public void await(Object obj) throws InterruptedException { /* JFR-related code is SVM-specific. */ long startTicks = JfrTicks.elapsedTicks(); if (Thread.interrupted()) { - JavaMonitorWaitEvent.emit(startTicks, obj, null, 0L, false); + JavaMonitorWaitEvent.emit(startTicks, obj, 0, 0L, false); throw new InterruptedException(); } ConditionNode node = newConditionNode(); @@ -651,7 +652,7 @@ public void await(Object obj) throws InterruptedException { } node.clearStatus(); // waiting is done, emit wait event - JavaMonitorWaitEvent.emit(startTicks, obj, node.notifierJfr, 0L, false); + JavaMonitorWaitEvent.emit(startTicks, obj, node.notifierJfrTid, 0L, false); reacquire(node, savedAcquisitions); if (interrupted) { if (cancelled) { @@ -669,7 +670,7 @@ public boolean await(Object obj, long time, TimeUnit unit) throws InterruptedExc long startTicks = JfrTicks.elapsedTicks(); long nanosTimeout = unit.toNanos(time); if (Thread.interrupted()) { - JavaMonitorWaitEvent.emit(startTicks, obj, null, 0L, false); + JavaMonitorWaitEvent.emit(startTicks, obj, 0, 0L, false); throw new InterruptedException(); } ConditionNode node = newConditionNode(); @@ -692,7 +693,7 @@ public boolean await(Object obj, long time, TimeUnit unit) throws InterruptedExc } node.clearStatus(); // waiting is done, emit wait event - JavaMonitorWaitEvent.emit(startTicks, obj, node.notifierJfr, time, cancelled); + JavaMonitorWaitEvent.emit(startTicks, obj, node.notifierJfrTid, time, cancelled); reacquire(node, savedAcquisitions); if (cancelled) { unlinkCancelledWaiters(node); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MultiThreadedMonitorSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MultiThreadedMonitorSupport.java index cf76db652ee1..77440bd4ea8c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MultiThreadedMonitorSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MultiThreadedMonitorSupport.java @@ -270,7 +270,7 @@ public void monitorEnter(Object obj, MonitorInflationCause cause) { monitor = (JavaMonitor) UNSAFE.compareAndExchangeReference(obj, monitorOffset, null, newMonitor); if (monitor == null) { // successful JavaMonitorInflateEvent.emit(obj, startTicks, MonitorInflationCause.MONITOR_ENTER); - newMonitor.latest = Thread.currentThread(); + newMonitor.latestJfrTid = current; return; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java index 4f8d60284a79..d1d811858b07 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.core.thread; +import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE; + import java.lang.Thread.UncaughtExceptionHandler; import java.security.AccessControlContext; import java.security.AccessController; @@ -220,8 +222,9 @@ static Target_java_lang_ThreadGroup toTarget(ThreadGroup threadGroup) { return Target_java_lang_ThreadGroup.class.cast(threadGroup); } + @Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true) @SuppressFBWarnings(value = "BC", justification = "Cast for @TargetClass") - private static Target_java_lang_VirtualThread toVirtualTarget(Thread thread) { + public static Target_java_lang_VirtualThread toVirtualTarget(Thread thread) { return Target_java_lang_VirtualThread.class.cast(thread); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_VirtualThread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_VirtualThread.java index 9b5d9c8308b4..38beaccb700c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_VirtualThread.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_VirtualThread.java @@ -47,6 +47,8 @@ import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.jdk.JDK21OrEarlier; import com.oracle.svm.core.jdk.JDKLatest; +import com.oracle.svm.core.jfr.HasJfrSupport; +import com.oracle.svm.core.jfr.SubstrateJVM; import com.oracle.svm.core.monitor.MonitorInflationCause; import com.oracle.svm.core.monitor.MonitorSupport; import com.oracle.svm.core.util.VMError; @@ -121,8 +123,8 @@ public final class Target_java_lang_VirtualThread { // Checkstyle: resume @Inject // - @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // - public volatile int jfrGeneration = -1; + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ResetToMinusOneTransformer.class) // + public long jfrEpochId = -1; @Alias private static native ForkJoinPool createDefaultScheduler(); @@ -329,6 +331,9 @@ void mount() { } carrier.setCurrentThread(asThread(this)); + if (HasJfrSupport.get()) { + SubstrateJVM.getThreadRepo().registerThread(asThread(this)); + } } @Substitute // not needed on newer JDKs that use safe disableSuspendAndPreempt() @@ -597,3 +602,10 @@ static Thread asThread(Object obj) { private VirtualThreadHelper() { } } + +final class ResetToMinusOneTransformer implements FieldValueTransformer { + @Override + public Object transform(Object receiver, Object originalValue) { + return -1L; + } +}