Skip to content

[GR-57064] Skip unnecessary JFR registrations for virtual threads. #11070

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
*/
package com.oracle.svm.core.jfr;

import jdk.graal.compiler.word.Word;
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
Expand All @@ -40,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.
*
Expand Down Expand Up @@ -101,14 +103,23 @@ public void registerRunningThreads() {
}
}

@Uninterruptible(reason = "Locking without transition requires that the whole critical section is uninterruptible.")
@Uninterruptible(reason = "Prevent epoch changes. Prevent races with VM operations that start/stop recording.")
public void registerThread(Thread thread) {
if (!SubstrateJVM.get().isRecording()) {
return;
}

long threadId = JavaThreads.getThreadId(thread);
boolean isVirtual = JavaThreads.isVirtual(thread);
if (isVirtual && isVirtualThreadAlreadyRegistered(thread)) {
return;
}

registerThread0(thread, isVirtual);
}

@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));
Expand All @@ -129,21 +140,26 @@ 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);
String name = thread.getName();

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);
if (!JfrNativeEventWriter.commit(data)) {
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();
Expand All @@ -152,6 +168,16 @@ public void registerThread(Thread thread) {
}
}

@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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -38,6 +37,8 @@
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, long previousOwnerTid, long startTicks) {
if (HasJfrSupport.get()) {
Expand All @@ -58,7 +59,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, previousOwnerTid);
JfrNativeEventWriter.putLong(data, Word.objectToUntrackedPointer(obj).rawValue());
JfrNativeEventWriter.endSmallEvent(data);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@
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, 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, long 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);
Expand All @@ -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, notifierTid);
JfrNativeEventWriter.putLong(data, timeout);
JfrNativeEventWriter.putBoolean(data, timedOut);
JfrNativeEventWriter.putLong(data, Word.objectToUntrackedPointer(obj).rawValue());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@

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;

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
Expand All @@ -42,7 +43,7 @@ public class JfrTraceIdEpoch {
private static final long EPOCH_0_BIT = 0b01;
private static final long EPOCH_1_BIT = 0b10;

private boolean epoch;
private long epochId;

@Fold
public static JfrTraceIdEpoch getInstance() {
Expand All @@ -56,26 +57,36 @@ public JfrTraceIdEpoch() {
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public void changeEpoch() {
assert VMOperation.isInProgressAtSafepoint();
epoch = !epoch;
epochId++;
}

@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 = "Called from uninterruptible code.", mayBeInlined = true)
public boolean previousEpoch() {
return !epoch;
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ public final class Target_java_lang_VirtualThread {
@TargetElement(onlyWith = JDKLatest.class) @Alias @InjectAccessors(AlwaysFalseAccessor.class) boolean notified;
// Checkstyle: resume

@Inject //
@RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ResetToMinusOneTransformer.class) //
public long jfrEpochId = -1;

@Alias
private static native ForkJoinPool createDefaultScheduler();

Expand Down Expand Up @@ -598,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;
}
}
Loading