Skip to content

Commit 54c5223

Browse files
Skip unnecessary JFR registrations for virtual threads.
1 parent 8cd35ad commit 54c5223

File tree

11 files changed

+90
-89
lines changed

11 files changed

+90
-89
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrNativeEventWriter.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
*/
2525
package com.oracle.svm.core.jfr;
2626

27-
import jdk.graal.compiler.word.Word;
2827
import org.graalvm.word.Pointer;
2928
import org.graalvm.word.UnsignedWord;
3029

@@ -35,6 +34,8 @@
3534
import com.oracle.svm.core.util.DuplicatedInNativeCode;
3635
import com.oracle.svm.core.util.VMError;
3736

37+
import jdk.graal.compiler.word.Word;
38+
3839
/**
3940
* A JFR event writer that does not allocate any objects in the Java heap. Can only be used from
4041
* {@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) {
250251
if (thread == null) {
251252
putThread(data, 0L);
252253
} else {
253-
SubstrateJVM.maybeRegisterVirtualThread(thread);
254254
putThread(data, SubstrateJVM.getThreadId(thread));
255255
}
256256
}
257257

258258
@Uninterruptible(reason = "Accesses a native JFR buffer.", callerMustBe = true)
259259
public static void putThread(JfrNativeEventWriterData data, long threadId) {
260-
SubstrateJVM.maybeRegisterVirtualThread(threadId);
261260
putLong(data, threadId);
262261
}
263262

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrThreadRepository.java

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@
2424
*/
2525
package com.oracle.svm.core.jfr;
2626

27-
import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE;
28-
import jdk.graal.compiler.word.Word;
29-
3027
import org.graalvm.nativeimage.IsolateThread;
3128
import org.graalvm.nativeimage.Platform;
3229
import org.graalvm.nativeimage.Platforms;
@@ -42,9 +39,12 @@
4239
import com.oracle.svm.core.thread.JavaThreads;
4340
import com.oracle.svm.core.thread.PlatformThreads;
4441
import com.oracle.svm.core.thread.Target_java_lang_Thread;
42+
import com.oracle.svm.core.thread.Target_java_lang_VirtualThread;
4543
import com.oracle.svm.core.thread.VMOperation;
4644
import com.oracle.svm.core.thread.VMThreads;
4745

46+
import jdk.graal.compiler.word.Word;
47+
4848
/**
4949
* Repository that collects all metadata about threads and thread groups.
5050
*
@@ -94,37 +94,32 @@ public void registerRunningThreads() {
9494
Thread thread = PlatformThreads.fromVMThread(isolateThread);
9595
if (thread != null) {
9696
registerThread(thread);
97+
// Re-register vthreads that are already mounted.
98+
Thread vthread = PlatformThreads.getMountedVirtualThread(thread);
99+
if (vthread != null) {
100+
registerThread(vthread);
101+
}
97102
}
98103
}
99104
}
100105

101-
/**
102-
* If this method is called on platform threads, nothing will happen since they are already
103-
* registered eagerly upon starting and at epoch changes.
104-
*/
105-
@Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.")
106-
public void registerThread(long threadId) {
106+
@Uninterruptible(reason = "Prevent epoch changes. Prevent races with VM operations that start/stop recording.")
107+
public void registerThread(Thread thread) {
107108
if (!SubstrateJVM.get().isRecording()) {
108109
return;
109110
}
110111

111-
registerThread0(threadId, 0, true, null, null);
112-
}
113-
114-
@Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.")
115-
public void registerThread(Thread thread) {
116-
if (!SubstrateJVM.get().isRecording()) {
112+
boolean isVirtual = JavaThreads.isVirtual(thread);
113+
if (isVirtual && isVirtualThreadAlreadyRegistered(thread)) {
117114
return;
118115
}
119116

120-
long threadId = JavaThreads.getThreadId(thread);
121-
boolean isVirtual = JavaThreads.isVirtual(thread);
122-
long osThreadId = isVirtual ? 0 : threadId;
123-
registerThread0(threadId, osThreadId, isVirtual, thread, thread.getName());
117+
registerThread0(thread, isVirtual);
124118
}
125119

126-
@Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
127-
private void registerThread0(long threadId, long osThreadId, boolean isVirtual, Thread thread, String name) {
120+
@Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.")
121+
private void registerThread0(Thread thread, boolean isVirtual) {
122+
long threadId = JavaThreads.getThreadId(thread);
128123
JfrVisited visitedThread = StackValue.get(JfrVisited.class);
129124
visitedThread.setId(threadId);
130125
visitedThread.setHash(UninterruptibleUtils.Long.hashCode(threadId));
@@ -145,7 +140,9 @@ private void registerThread0(long threadId, long osThreadId, boolean isVirtual,
145140
JfrNativeEventWriterDataAccess.initialize(data, epochData.threadBuffer);
146141

147142
/* Similar to JfrThreadConstant::serialize in HotSpot. */
143+
long osThreadId = isVirtual ? 0 : threadId;
148144
long threadGroupId = registerThreadGroup(thread, isVirtual);
145+
String name = thread.getName();
149146

150147
JfrNativeEventWriter.putLong(data, threadId);
151148
JfrNativeEventWriter.putString(data, name); // OS thread name
@@ -158,6 +155,11 @@ private void registerThread0(long threadId, long osThreadId, boolean isVirtual,
158155
return;
159156
}
160157

158+
if (isVirtual) {
159+
Target_java_lang_VirtualThread vthread = JavaThreads.toVirtualTarget(thread);
160+
vthread.jfrEpochId = JfrTraceIdEpoch.getInstance().currentEpochId();
161+
}
162+
161163
epochData.unflushedThreadCount++;
162164
/* The buffer may have been replaced with a new one. */
163165
epochData.threadBuffer = data.getJfrBuffer();
@@ -166,6 +168,16 @@ private void registerThread0(long threadId, long osThreadId, boolean isVirtual,
166168
}
167169
}
168170

171+
@Uninterruptible(reason = "Epoch must not change while in this method.", callerMustBe = true)
172+
private static boolean isVirtualThreadAlreadyRegistered(Thread thread) {
173+
assert JavaThreads.isVirtual(thread);
174+
175+
/* Threads only need to be registered once per epoch. */
176+
Target_java_lang_VirtualThread vthread = JavaThreads.toVirtualTarget(thread);
177+
long epochId = JfrTraceIdEpoch.getInstance().currentEpochId();
178+
return vthread.jfrEpochId == epochId;
179+
}
180+
169181
@Uninterruptible(reason = "Epoch must not change while in this method.")
170182
private long registerThreadGroup(Thread thread, boolean isVirtual) {
171183
if (isVirtual) {

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/SubstrateJVM.java

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626

2727
import java.util.List;
2828

29-
import com.oracle.svm.core.SubstrateUtil;
3029
import org.graalvm.nativeimage.ImageSingletons;
3130
import org.graalvm.nativeimage.IsolateThread;
3231
import org.graalvm.nativeimage.Platform;
@@ -42,15 +41,13 @@
4241
import com.oracle.svm.core.jfr.oldobject.JfrOldObjectRepository;
4342
import com.oracle.svm.core.jfr.sampler.JfrExecutionSampler;
4443
import com.oracle.svm.core.jfr.throttling.JfrEventThrottling;
45-
import com.oracle.svm.core.jfr.traceid.JfrTraceIdEpoch;
4644
import com.oracle.svm.core.log.Log;
4745
import com.oracle.svm.core.sampler.SamplerBufferPool;
4846
import com.oracle.svm.core.sampler.SamplerBuffersAccess;
4947
import com.oracle.svm.core.sampler.SamplerStatistics;
5048
import com.oracle.svm.core.sampler.SubstrateSigprofHandler;
5149
import com.oracle.svm.core.thread.JavaThreads;
5250
import com.oracle.svm.core.thread.JavaVMOperation;
53-
import com.oracle.svm.core.thread.Target_java_lang_VirtualThread;
5451
import com.oracle.svm.core.thread.VMThreads;
5552
import com.oracle.svm.core.util.VMError;
5653

@@ -320,32 +317,6 @@ public static long getCurrentThreadId() {
320317
return 0;
321318
}
322319

323-
/**
324-
* Register virtual threads if they aren't registered already. Platform threads are registered
325-
* eagerly when started and at chunk rotations.
326-
*/
327-
@Uninterruptible(reason = "Epoch should not change while checking generation.")
328-
public static void maybeRegisterVirtualThread(Thread thread) {
329-
// Do quick preliminary checks to avoid global locking unless necessary.
330-
if (JavaThreads.isVirtual(thread)) {
331-
Target_java_lang_VirtualThread tjlv = SubstrateUtil.cast(thread, Target_java_lang_VirtualThread.class);
332-
int currentEpochGen = JfrTraceIdEpoch.getInstance().currentEpochGeneration();
333-
if (tjlv.jfrGeneration != currentEpochGen) {
334-
getThreadRepo().registerThread(thread);
335-
tjlv.jfrGeneration = currentEpochGen;
336-
}
337-
}
338-
}
339-
340-
/**
341-
* {@link SubstrateJVM#maybeRegisterVirtualThread(Thread)} Is preferred over this method since
342-
* it can perform preliminary checks to avoid locking the Thread Repository unnecessarily.
343-
*/
344-
@Uninterruptible(reason = "Epoch should not change while checking generation.")
345-
public static void maybeRegisterVirtualThread(long tid) {
346-
getThreadRepo().registerThread(tid);
347-
}
348-
349320
/**
350321
* See {@link JVM#storeMetadataDescriptor}.
351322
*/

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorEnterEvent.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626

2727
package com.oracle.svm.core.jfr.events;
2828

29-
import jdk.graal.compiler.word.Word;
3029
import org.graalvm.nativeimage.StackValue;
3130

3231
import com.oracle.svm.core.Uninterruptible;
@@ -38,15 +37,17 @@
3837
import com.oracle.svm.core.jfr.JfrTicks;
3938
import com.oracle.svm.core.jfr.SubstrateJVM;
4039

40+
import jdk.graal.compiler.word.Word;
41+
4142
public class JavaMonitorEnterEvent {
42-
public static void emit(Object obj, Thread previousOwner, long startTicks) {
43+
public static void emit(Object obj, long previousOwnerTid, long startTicks) {
4344
if (HasJfrSupport.get()) {
44-
emit0(obj, previousOwner, startTicks);
45+
emit0(obj, previousOwnerTid, startTicks);
4546
}
4647
}
4748

4849
@Uninterruptible(reason = "Accesses a JFR buffer.")
49-
public static void emit0(Object obj, Thread previousOwner, long startTicks) {
50+
public static void emit0(Object obj, long previousOwnerTid, long startTicks) {
5051
long duration = JfrTicks.duration(startTicks);
5152
if (JfrEvent.JavaMonitorEnter.shouldEmit(duration)) {
5253
JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class);
@@ -58,7 +59,7 @@ public static void emit0(Object obj, Thread previousOwner, long startTicks) {
5859
JfrNativeEventWriter.putEventThread(data);
5960
JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorEnter, 0));
6061
JfrNativeEventWriter.putClass(data, obj.getClass());
61-
JfrNativeEventWriter.putThread(data, previousOwner);
62+
JfrNativeEventWriter.putThread(data, previousOwnerTid);
6263
JfrNativeEventWriter.putLong(data, Word.objectToUntrackedPointer(obj).rawValue());
6364
JfrNativeEventWriter.endSmallEvent(data);
6465
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorWaitEvent.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,15 @@
4040
import jdk.graal.compiler.word.Word;
4141

4242
public class JavaMonitorWaitEvent {
43-
public static void emit(long startTicks, Object obj, Thread notifier, long timeout, boolean timedOut) {
43+
public static void emit(long startTicks, Object obj, long notifierTid, long timeout, boolean timedOut) {
4444
if (HasJfrSupport.get() && obj != null && !Target_jdk_jfr_internal_JVM_ChunkRotationMonitor.class.equals(obj.getClass()) &&
4545
!Target_jdk_jfr_internal_management_HiddenWait.class.equals(obj.getClass())) {
46-
emit0(startTicks, obj, notifier, timeout, timedOut);
46+
emit0(startTicks, obj, notifierTid, timeout, timedOut);
4747
}
4848
}
4949

5050
@Uninterruptible(reason = "Accesses a JFR buffer.")
51-
private static void emit0(long startTicks, Object obj, Thread notifier, long timeout, boolean timedOut) {
51+
private static void emit0(long startTicks, Object obj, long notifierTid, long timeout, boolean timedOut) {
5252
long duration = JfrTicks.duration(startTicks);
5353
if (JfrEvent.JavaMonitorWait.shouldEmit(duration)) {
5454
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
6060
JfrNativeEventWriter.putEventThread(data);
6161
JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorWait, 0));
6262
JfrNativeEventWriter.putClass(data, obj.getClass());
63-
JfrNativeEventWriter.putThread(data, notifier);
63+
JfrNativeEventWriter.putThread(data, notifierTid);
6464
JfrNativeEventWriter.putLong(data, timeout);
6565
JfrNativeEventWriter.putBoolean(data, timedOut);
6666
JfrNativeEventWriter.putLong(data, Word.objectToUntrackedPointer(obj).rawValue());

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/traceid/JfrTraceIdEpoch.java

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@
2525

2626
package com.oracle.svm.core.jfr.traceid;
2727

28-
import jdk.graal.compiler.api.replacements.Fold;
2928
import org.graalvm.nativeimage.ImageSingletons;
3029
import org.graalvm.nativeimage.Platform;
3130
import org.graalvm.nativeimage.Platforms;
3231

3332
import com.oracle.svm.core.Uninterruptible;
3433
import com.oracle.svm.core.thread.VMOperation;
3534

35+
import jdk.graal.compiler.api.replacements.Fold;
36+
3637
/**
3738
* Class holding the current JFR epoch. JFR uses an epoch system to safely separate constant pool
3839
* entries between adjacent chunks. Used to get the current or previous epoch and switch from one
@@ -42,7 +43,7 @@ public class JfrTraceIdEpoch {
4243
private static final long EPOCH_0_BIT = 0b01;
4344
private static final long EPOCH_1_BIT = 0b10;
4445

45-
private int epochGeneration;
46+
private long epochId;
4647

4748
@Fold
4849
public static JfrTraceIdEpoch getInstance() {
@@ -53,15 +54,10 @@ public static JfrTraceIdEpoch getInstance() {
5354
public JfrTraceIdEpoch() {
5455
}
5556

56-
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
57-
private boolean getEpoch() {
58-
return (epochGeneration & 1) == 0;
59-
}
60-
6157
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
6258
public void changeEpoch() {
6359
assert VMOperation.isInProgressAtSafepoint();
64-
epochGeneration++;
60+
epochId++;
6561
}
6662

6763
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
@@ -79,13 +75,18 @@ public boolean currentEpoch() {
7975
return getEpoch();
8076
}
8177

82-
@Uninterruptible(reason = "Avoid epoch changing while checking generation.", callerMustBe = true)
83-
public int currentEpochGeneration() {
84-
return epochGeneration;
85-
}
86-
8778
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
8879
public boolean previousEpoch() {
8980
return !getEpoch();
9081
}
82+
83+
@Uninterruptible(reason = "Prevent epoch from changing.", callerMustBe = true)
84+
public long currentEpochId() {
85+
return epochId;
86+
}
87+
88+
@Uninterruptible(reason = "Prevent epoch from changing.", callerMustBe = true)
89+
private boolean getEpoch() {
90+
return (epochId & 1) == 0;
91+
}
9192
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/JavaMonitor.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
import com.oracle.svm.core.Uninterruptible;
3838
import com.oracle.svm.core.jfr.JfrTicks;
39+
import com.oracle.svm.core.jfr.SubstrateJVM;
3940
import com.oracle.svm.core.jfr.events.JavaMonitorEnterEvent;
4041
import com.oracle.svm.core.thread.JavaThreads;
4142
import com.oracle.svm.core.util.BasedOnJDKClass;
@@ -60,20 +61,20 @@
6061
@BasedOnJDKClass(ReentrantLock.class)
6162
@BasedOnJDKClass(value = ReentrantLock.class, innerClass = "Sync")
6263
public class JavaMonitor extends JavaMonitorQueuedSynchronizer {
63-
protected Thread latest;
64+
protected long latestJfrTid;
6465

6566
public JavaMonitor() {
66-
latest = null;
67+
latestJfrTid = 0;
6768
}
6869

6970
public void monitorEnter(Object obj) {
7071
if (!tryLock()) {
7172
long startTicks = JfrTicks.elapsedTicks();
7273
acquire(1);
73-
JavaMonitorEnterEvent.emit(obj, latest, startTicks);
74+
JavaMonitorEnterEvent.emit(obj, latestJfrTid, startTicks);
7475
}
7576

76-
latest = Thread.currentThread();
77+
latestJfrTid = SubstrateJVM.getCurrentThreadId();
7778
}
7879

7980
public void monitorExit() {

0 commit comments

Comments
 (0)