diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index 7d76702cc7d0..4a163bf7f3bb 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -17,7 +17,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-42964) Deprecate `--enable-monitoring` without an argument. The option will no longer default to `all` in a future release. Instead, please always explicitly specify the list of monitoring features to be enabled (for example, `--enable-monitoring=heapdump,jfr,jvmstat`"). * (GR-19890) Native Image now sets up build environments for Windows users automatically. Running in an x64 Native Tools Command Prompt is no longer a requirement. * (GR-43410) Added support for the JFR event `ExecutionSample`. -* (GR-44058) Red Hat added support for the JFR event `ObjectAllocationInNewTLAB`. +* (GR-44058) (GR-44087) Red Hat added support for the JFR events `ObjectAllocationInNewTLAB` and `JavaMonitorInflate`. * (GR-42467) The search path for `System.loadLibrary()` by default includes the directory containing the native image. ## Version 22.3.0 diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java index 8137d18ffb8c..4ca85367ee92 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrEvent.java @@ -58,6 +58,7 @@ public final class JfrEvent { public static final JfrEvent JavaMonitorEnter = create("jdk.JavaMonitorEnter"); public static final JfrEvent ThreadPark = create("jdk.ThreadPark"); public static final JfrEvent JavaMonitorWait = create("jdk.JavaMonitorWait"); + public static final JfrEvent JavaMonitorInflate = create("jdk.JavaMonitorInflate"); public static final JfrEvent ObjectAllocationInNewTLAB = create("jdk.ObjectAllocationInNewTLAB"); private final long id; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java index daa4b4548eb2..17bf45e6a47f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrFeature.java @@ -169,6 +169,7 @@ public void afterRegistration(AfterRegistrationAccess access) { JfrSerializerSupport.get().register(new JfrFrameTypeSerializer()); JfrSerializerSupport.get().register(new JfrThreadStateSerializer()); + JfrSerializerSupport.get().register(new JfrMonitorInflationCauseSerializer()); JfrSerializerSupport.get().register(new JfrGCCauseSerializer()); JfrSerializerSupport.get().register(new JfrGCNameSerializer()); JfrSerializerSupport.get().register(new JfrVMOperationNameSerializer()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMonitorInflationCauseSerializer.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMonitorInflationCauseSerializer.java new file mode 100644 index 000000000000..73b0af20c249 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrMonitorInflationCauseSerializer.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.jfr; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.svm.core.monitor.MonitorInflationCause; + +public class JfrMonitorInflationCauseSerializer implements JfrConstantPool { + @Platforms(Platform.HOSTED_ONLY.class) + public JfrMonitorInflationCauseSerializer() { + } + + @Override + public int write(JfrChunkWriter writer) { + writer.writeCompressedLong(JfrType.MonitorInflationCause.getId()); + + MonitorInflationCause[] inflationCauses = MonitorInflationCause.values(); + writer.writeCompressedLong(inflationCauses.length); + for (int i = 0; i < inflationCauses.length; i++) { + writer.writeCompressedInt(i); + writer.writeString(inflationCauses[i].getText()); + } + + return NON_EMPTY; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java index b6294998bd17..bdc206cf1ff9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/JfrType.java @@ -42,7 +42,8 @@ public enum JfrType { FrameType("jdk.types.FrameType"), GCCause("jdk.types.GCCause"), GCName("jdk.types.GCName"), - VMOperation("jdk.types.VMOperationType"); + VMOperation("jdk.types.VMOperationType"), + MonitorInflationCause("jdk.types.InflateCause"); private final long id; diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorInflateEvent.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorInflateEvent.java new file mode 100644 index 000000000000..aa958f255297 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jfr/events/JavaMonitorInflateEvent.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.jfr.events; + +import org.graalvm.compiler.word.Word; +import org.graalvm.nativeimage.StackValue; + +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.jfr.HasJfrSupport; +import com.oracle.svm.core.jfr.JfrEvent; +import com.oracle.svm.core.jfr.JfrNativeEventWriter; +import com.oracle.svm.core.jfr.JfrNativeEventWriterData; +import com.oracle.svm.core.jfr.JfrNativeEventWriterDataAccess; +import com.oracle.svm.core.jfr.JfrTicks; +import com.oracle.svm.core.jfr.SubstrateJVM; +import com.oracle.svm.core.monitor.MonitorInflationCause; + +public class JavaMonitorInflateEvent { + public static void emit(Object obj, long startTicks, MonitorInflationCause cause) { + if (HasJfrSupport.get()) { + emit0(obj, startTicks, cause); + } + } + + @Uninterruptible(reason = "Accesses a JFR buffer.") + public static void emit0(Object obj, long startTicks, MonitorInflationCause cause) { + if (JfrEvent.JavaMonitorInflate.shouldEmit()) { + JfrNativeEventWriterData data = StackValue.get(JfrNativeEventWriterData.class); + JfrNativeEventWriterDataAccess.initializeThreadLocalNativeBuffer(data); + + JfrNativeEventWriter.beginSmallEvent(data, JfrEvent.JavaMonitorInflate); + JfrNativeEventWriter.putLong(data, startTicks); + JfrNativeEventWriter.putLong(data, JfrTicks.elapsedTicks() - startTicks); + JfrNativeEventWriter.putEventThread(data); + JfrNativeEventWriter.putLong(data, SubstrateJVM.get().getStackTraceId(JfrEvent.JavaMonitorInflate, 0)); + JfrNativeEventWriter.putClass(data, obj.getClass()); + JfrNativeEventWriter.putLong(data, Word.objectToUntrackedPointer(obj).rawValue()); + JfrNativeEventWriter.putLong(data, getId(cause)); + JfrNativeEventWriter.endSmallEvent(data); + } + } + + @Uninterruptible(reason = "Called from uninterruptible code", mayBeInlined = true) + private static long getId(MonitorInflationCause cause) { + /* First entry needs to have id 0. */ + return cause.ordinal(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIFunctions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIFunctions.java index e50288c04ae9..5482f8a7bb83 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIFunctions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIFunctions.java @@ -105,6 +105,7 @@ import com.oracle.svm.core.jni.headers.JNIVersion; import com.oracle.svm.core.jni.headers.JNIVersionJDK19OrLater; import com.oracle.svm.core.log.Log; +import com.oracle.svm.core.monitor.MonitorInflationCause; import com.oracle.svm.core.monitor.MonitorSupport; import com.oracle.svm.core.snippets.KnownIntrinsics; import com.oracle.svm.core.stack.StackOverflowCheck; @@ -1015,7 +1016,7 @@ static int MonitorEnter(JNIEnvironment env, JNIObjectHandle handle) { } boolean acquired = false; try { - MonitorSupport.singleton().monitorEnter(obj); + MonitorSupport.singleton().monitorEnter(obj, MonitorInflationCause.JNI_ENTER); assert Thread.holdsLock(obj); acquired = true; @@ -1024,7 +1025,8 @@ static int MonitorEnter(JNIEnvironment env, JNIObjectHandle handle) { } catch (Throwable t) { try { if (acquired) { - MonitorSupport.singleton().monitorExit(obj); + /* The thread acquired the monitor, so monitor inflation can't happen here. */ + MonitorSupport.singleton().monitorExit(obj, MonitorInflationCause.VM_INTERNAL); } if (pinned) { VirtualThreads.singleton().unpinCurrent(); @@ -1049,7 +1051,7 @@ static int MonitorExit(JNIEnvironment env, JNIObjectHandle handle) { if (!Thread.holdsLock(obj)) { throw new IllegalMonitorStateException(); } - MonitorSupport.singleton().monitorExit(obj); + MonitorSupport.singleton().monitorExit(obj, MonitorInflationCause.JNI_EXIT); JNIThreadOwnedMonitors.exited(obj); if (VirtualThreads.isSupported() && JavaThreads.isCurrentThreadVirtual()) { try { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIInvocationInterface.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIInvocationInterface.java index d8b524f42588..64f31b677262 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIInvocationInterface.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jni/functions/JNIInvocationInterface.java @@ -42,8 +42,8 @@ import org.graalvm.word.WordFactory; import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.UnmanagedMemoryUtil; import com.oracle.svm.core.c.CGlobalData; import com.oracle.svm.core.c.CGlobalDataFactory; import com.oracle.svm.core.c.function.CEntryPointActions; @@ -73,6 +73,7 @@ import com.oracle.svm.core.jni.headers.JNIJavaVMPointer; import com.oracle.svm.core.jni.headers.JNIVersion; import com.oracle.svm.core.log.FunctionPointerLogHandler; +import com.oracle.svm.core.monitor.MonitorInflationCause; import com.oracle.svm.core.monitor.MonitorSupport; import com.oracle.svm.core.snippets.ImplicitExceptions; import com.oracle.svm.core.thread.PlatformThreads; @@ -265,11 +266,8 @@ static int AttachCurrentThreadAsDaemon(JNIJavaVM vm, JNIEnvironmentPointer penv, */ @CEntryPoint(include = CEntryPoint.NotIncludedAutomatically.class, publishAs = Publish.NotPublished) @CEntryPointOptions(prologue = JNIJavaVMEnterAttachThreadEnsureJavaThreadPrologue.class, epilogue = LeaveDetachThreadEpilogue.class) - static int DetachCurrentThread(JNIJavaVM vm) { + static int DetachCurrentThread(@SuppressWarnings("unused") JNIJavaVM vm) { int result = JNIErrors.JNI_OK(); - if (!vm.equal(JNIFunctionTables.singleton().getGlobalJavaVM())) { - result = JNIErrors.JNI_ERR(); - } // JNI specification requires releasing all owned monitors Support.releaseCurrentThreadOwnedMonitors(); return result; @@ -353,7 +351,7 @@ static void attachCurrentThread(JNIJavaVM vm, JNIEnvironmentPointer penv, JNIJav static void releaseCurrentThreadOwnedMonitors() { JNIThreadOwnedMonitors.forEach((obj, depth) -> { for (int i = 0; i < depth; i++) { - MonitorSupport.singleton().monitorExit(obj); + MonitorSupport.singleton().monitorExit(obj, MonitorInflationCause.VM_INTERNAL); } assert !Thread.holdsLock(obj); }); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MonitorInflationCause.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MonitorInflationCause.java new file mode 100644 index 000000000000..a4ee24912a6d --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MonitorInflationCause.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.monitor; + +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +public enum MonitorInflationCause { + VM_INTERNAL("VM Internal"), + MONITOR_ENTER("Monitor Enter"), + WAIT("Monitor Wait"), + NOTIFY("Monitor Notify"), + JNI_ENTER("JNI Monitor Enter"), + JNI_EXIT("JNI Monitor Exit"); + + private final String text; + + @Platforms(Platform.HOSTED_ONLY.class) + MonitorInflationCause(String text) { + this.text = text; + } + + public String getText() { + return text; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MonitorSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MonitorSupport.java index 23444cb249e7..abbc7f94bc00 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MonitorSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/MonitorSupport.java @@ -44,12 +44,12 @@ public static MonitorSupport singleton() { /** * Implements the semantics of the monitorenter bytecode. */ - public abstract void monitorEnter(Object obj); + public abstract void monitorEnter(Object obj, MonitorInflationCause cause); /** * Implements the semantics of the monitorexit bytecode. */ - public abstract void monitorExit(Object obj); + public abstract void monitorExit(Object obj, MonitorInflationCause cause); /* * Support for objects that are re-locked during deoptimization. This method is called when 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 5cb7a5a8c28f..cf1844f26a5b 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 @@ -40,14 +40,16 @@ import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; +import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.WeakIdentityHashMap; +import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.heap.RestrictHeapAccess; import com.oracle.svm.core.heap.RestrictHeapAccess.Access; -import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.hub.DynamicHub; import com.oracle.svm.core.hub.DynamicHubCompanion; import com.oracle.svm.core.jdk.JDK17OrEarlier; +import com.oracle.svm.core.jfr.JfrTicks; +import com.oracle.svm.core.jfr.events.JavaMonitorInflateEvent; import com.oracle.svm.core.monitor.JavaMonitorQueuedSynchronizer.JavaMonitorConditionObject; import com.oracle.svm.core.snippets.SubstrateForeignCallTarget; import com.oracle.svm.core.stack.StackOverflowCheck; @@ -205,7 +207,7 @@ private static void slowPathMonitorEnter(Object obj) { StackOverflowCheck.singleton().makeYellowZoneAvailable(); VMOperationControl.guaranteeOkayToBlock("No Java synchronization must be performed within a VMOperation: if the object is already locked, the VM is deadlocked"); try { - singleton().monitorEnter(obj); + singleton().monitorEnter(obj, MonitorInflationCause.MONITOR_ENTER); } catch (OutOfMemoryError ex) { /* @@ -238,8 +240,8 @@ private static void slowPathMonitorEnter(Object obj) { @RestrictHeapAccess(reason = NO_LONGER_UNINTERRUPTIBLE, access = Access.UNRESTRICTED) @Override - public void monitorEnter(Object obj) { - JavaMonitor lockObject = getOrCreateMonitor(obj, true); + public void monitorEnter(Object obj, MonitorInflationCause cause) { + JavaMonitor lockObject = getOrCreateMonitor(obj, cause); lockObject.monitorEnter(obj); } @@ -248,7 +250,11 @@ public void monitorEnter(Object obj) { private static void slowPathMonitorExit(Object obj) { StackOverflowCheck.singleton().makeYellowZoneAvailable(); try { - singleton().monitorExit(obj); + /* + * Monitor inflation cannot happen here because Graal enforces structured locking and + * unlocking, see comment below. + */ + singleton().monitorExit(obj, MonitorInflationCause.VM_INTERNAL); } catch (OutOfMemoryError ex) { /* @@ -275,8 +281,8 @@ private static void slowPathMonitorExit(Object obj) { @RestrictHeapAccess(reason = NO_LONGER_UNINTERRUPTIBLE, access = Access.UNRESTRICTED) @Override - public void monitorExit(Object obj) { - JavaMonitor lockObject = getOrCreateMonitor(obj, true); + public void monitorExit(Object obj, MonitorInflationCause cause) { + JavaMonitor lockObject = getOrCreateMonitor(obj, cause); lockObject.monitorExit(); } @@ -296,7 +302,7 @@ public Object prepareRelockObject(Object obj) { * internal state of the lock immediately here. The actual state patching therefore happens * later in doRelockObject. */ - return getOrCreateMonitor(obj, true); + return getOrCreateMonitor(obj, MonitorInflationCause.VM_INTERNAL); } @Uninterruptible(reason = "called during deoptimization") @@ -308,13 +314,13 @@ public void doRelockObject(Object obj, Object lockData) { @Override public boolean isLockedByCurrentThread(Object obj) { - JavaMonitor lockObject = getOrCreateMonitor(obj, false); + JavaMonitor lockObject = getMonitor(obj); return lockObject != null && lockObject.isHeldByCurrentThread(); } @Override public boolean isLockedByAnyThread(Object obj) { - JavaMonitor lockObject = getOrCreateMonitor(obj, false); + JavaMonitor lockObject = getMonitor(obj); return lockObject != null && lockObject.isLocked(); } @@ -325,7 +331,7 @@ protected void doWait(Object obj, long timeoutMillis) throws InterruptedExceptio * Ensure that the current thread holds the lock. Required by the specification of * Object.wait, and also required for our implementation. */ - JavaMonitor lock = ensureLocked(obj); + JavaMonitor lock = ensureLocked(obj, MonitorInflationCause.WAIT); JavaMonitorConditionObject condition = lock.getOrCreateCondition(true); if (timeoutMillis == 0L) { condition.await(obj); @@ -337,7 +343,7 @@ protected void doWait(Object obj, long timeoutMillis) throws InterruptedExceptio @Override public void notify(Object obj, boolean notifyAll) { /* Make sure the current thread holds the lock on the receiver. */ - JavaMonitor lock = ensureLocked(obj); + JavaMonitor lock = ensureLocked(obj, MonitorInflationCause.NOTIFY); /* Find the wait/notify condition of the receiver. */ JavaMonitorConditionObject condition = lock.getOrCreateCondition(false); /* If the receiver does not have a condition, then it has not been waited on. */ @@ -351,8 +357,8 @@ public void notify(Object obj, boolean notifyAll) { } /** Returns the lock of the object. */ - protected JavaMonitor ensureLocked(Object obj) { - JavaMonitor lockObject = getOrCreateMonitor(obj, true); + protected JavaMonitor ensureLocked(Object obj, MonitorInflationCause cause) { + JavaMonitor lockObject = getOrCreateMonitor(obj, cause); if (!lockObject.isHeldByCurrentThread()) { throw new IllegalMonitorStateException("Receiver is not locked by the current thread."); } @@ -375,43 +381,53 @@ protected static Object replaceObject(Object unreplacedObject) { return unreplacedObject; } - protected final JavaMonitor getOrCreateMonitor(Object obj, boolean createIfNotExisting) { + protected final JavaMonitor getMonitor(Object obj) { + return getOrCreateMonitor(obj, false, null); + } + + protected final JavaMonitor getOrCreateMonitor(Object obj, MonitorInflationCause cause) { + return getOrCreateMonitor(obj, true, cause); + } + + private JavaMonitor getOrCreateMonitor(Object obj, boolean createIfNotExisting, MonitorInflationCause cause) { int monitorOffset = getMonitorOffset(obj); if (monitorOffset != 0) { /* The common case: pointer to the monitor reserved in the object. */ - return getOrCreateMonitorFromObject(obj, createIfNotExisting, monitorOffset); + return getOrCreateMonitorFromObject(obj, createIfNotExisting, monitorOffset, cause); } else { - return getOrCreateMonitorSlow(obj, createIfNotExisting); + return getOrCreateMonitorSlow(obj, createIfNotExisting, cause); } } - private JavaMonitor getOrCreateMonitorSlow(Object unreplacedObject, boolean createIfNotExisting) { + private JavaMonitor getOrCreateMonitorSlow(Object unreplacedObject, boolean createIfNotExisting, MonitorInflationCause cause) { Object replacedObject = replaceObject(unreplacedObject); if (replacedObject != unreplacedObject) { int monitorOffset = getMonitorOffset(replacedObject); if (monitorOffset != 0) { - return getOrCreateMonitorFromObject(replacedObject, createIfNotExisting, monitorOffset); + return getOrCreateMonitorFromObject(replacedObject, createIfNotExisting, monitorOffset, cause); } } /* No memory reserved for a lock in the object, fall back to secondary storage. */ - return getOrCreateMonitorFromMap(replacedObject, createIfNotExisting); + return getOrCreateMonitorFromMap(replacedObject, createIfNotExisting, cause); } - protected JavaMonitor getOrCreateMonitorFromObject(Object obj, boolean createIfNotExisting, int monitorOffset) { + protected JavaMonitor getOrCreateMonitorFromObject(Object obj, boolean createIfNotExisting, int monitorOffset, MonitorInflationCause cause) { JavaMonitor existingMonitor = (JavaMonitor) BarrieredAccess.readObject(obj, monitorOffset); if (existingMonitor != null || !createIfNotExisting) { return existingMonitor; } + long startTicks = JfrTicks.elapsedTicks(); /* Atomically put a new lock in place of the null at the monitorOffset. */ JavaMonitor newMonitor = newMonitorLock(); if (UNSAFE.compareAndSetObject(obj, monitorOffset, null, newMonitor)) { + JavaMonitorInflateEvent.emit(obj, startTicks, cause); return newMonitor; } /* We lost the race, use the lock some other thread installed. */ return (JavaMonitor) BarrieredAccess.readObject(obj, monitorOffset); } - protected JavaMonitor getOrCreateMonitorFromMap(Object obj, boolean createIfNotExisting) { + protected JavaMonitor getOrCreateMonitorFromMap(Object obj, boolean createIfNotExisting, MonitorInflationCause cause) { assert JavaVersionUtil.JAVA_SPEC > 17 || obj.getClass() != Target_java_lang_ref_ReferenceQueue_Lock.class : "ReferenceQueue.Lock must have a monitor field or we can deadlock accessing WeakIdentityHashMap below"; VMError.guarantee(!additionalMonitorsLock.isHeldByCurrentThread(), @@ -427,9 +443,11 @@ protected JavaMonitor getOrCreateMonitorFromMap(Object obj, boolean createIfNotE if (existingMonitor != null || !createIfNotExisting) { return existingMonitor; } + long startTicks = JfrTicks.elapsedTicks(); JavaMonitor newMonitor = newMonitorLock(); JavaMonitor previousEntry = additionalMonitors.put(obj, newMonitor); VMError.guarantee(previousEntry == null, "Replaced monitor in secondary storage map"); + JavaMonitorInflateEvent.emit(obj, startTicks, cause); return newMonitor; } finally { additionalMonitorsLock.unlock(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/SingleThreadedMonitorSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/SingleThreadedMonitorSupport.java index 7ada4e90ac9f..c0f7ad2d5063 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/SingleThreadedMonitorSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/monitor/SingleThreadedMonitorSupport.java @@ -31,14 +31,13 @@ * Without support for threads, there is no need for any monitor operations. */ public class SingleThreadedMonitorSupport extends MonitorSupport { - @Override - public void monitorEnter(Object obj) { + public void monitorEnter(Object obj, MonitorInflationCause cause) { /* Synchronization is a no-op in single threaded mode. */ } @Override - public void monitorExit(Object obj) { + public void monitorExit(Object obj, MonitorInflationCause cause) { /* Synchronization is a no-op in single threaded mode. */ } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/SubstrateVirtualThread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/SubstrateVirtualThread.java index 590c089890c3..12260400e20a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/SubstrateVirtualThread.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/SubstrateVirtualThread.java @@ -44,6 +44,7 @@ import com.oracle.svm.core.jdk.ContinuationsSupported; import com.oracle.svm.core.jdk.JDK17OrEarlier; import com.oracle.svm.core.jdk.NotLoomJDK; +import com.oracle.svm.core.monitor.MonitorInflationCause; import com.oracle.svm.core.monitor.MonitorSupport; import com.oracle.svm.core.stack.JavaFrameAnchor; import com.oracle.svm.core.stack.JavaFrameAnchors; @@ -450,13 +451,13 @@ private Object acquireInterruptLockMaybeSwitch() { PlatformThreads.setCurrentThread(carrier, carrier); token = this; } - MonitorSupport.singleton().monitorEnter(interruptLock()); + MonitorSupport.singleton().monitorEnter(interruptLock(), MonitorInflationCause.VM_INTERNAL); return token; } /** @see #acquireInterruptLockMaybeSwitch */ private void releaseInterruptLockMaybeSwitchBack(Object token) { - MonitorSupport.singleton().monitorExit(interruptLock()); + MonitorSupport.singleton().monitorExit(interruptLock(), MonitorInflationCause.VM_INTERNAL); if (token != null) { assert token == this && Thread.currentThread() == carrierThread; PlatformThreads.setCurrentThread(carrierThread, this); 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 ae0874ae9e8e..c64b0b6727c8 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 @@ -41,6 +41,7 @@ import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.jdk.JDK20OrLater; import com.oracle.svm.core.jdk.LoomJDK; +import com.oracle.svm.core.monitor.MonitorInflationCause; import com.oracle.svm.core.monitor.MonitorSupport; import com.oracle.svm.core.util.VMError; @@ -287,14 +288,14 @@ static Object acquireInterruptLockMaybeSwitch(Target_java_lang_VirtualThread sel token = self; } Object lock = asTarget(self).interruptLock; - MonitorSupport.singleton().monitorEnter(lock); + MonitorSupport.singleton().monitorEnter(lock, MonitorInflationCause.VM_INTERNAL); return token; } /** @see #acquireInterruptLockMaybeSwitch */ static void releaseInterruptLockMaybeSwitchBack(Target_java_lang_VirtualThread self, Object token) { Object lock = asTarget(self).interruptLock; - MonitorSupport.singleton().monitorExit(lock); + MonitorSupport.singleton().monitorExit(lock, MonitorInflationCause.VM_INTERNAL); if (token != null) { assert token == self; Thread carrier = asVTarget(token).carrierThread; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorInflateEvent.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorInflateEvent.java new file mode 100644 index 000000000000..9edcbfde57d6 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/TestJavaMonitorInflateEvent.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.test.jfr; + +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.oracle.svm.core.jfr.JfrEvent; +import com.oracle.svm.core.monitor.MonitorInflationCause; + +import jdk.jfr.consumer.RecordedClass; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordedThread; + +public class TestJavaMonitorInflateEvent extends JfrTest { + private static final EnterHelper ENTER_HELPER = new EnterHelper(); + private static Thread firstThread; + private static Thread secondThread; + + @Override + public String[] getTestedEvents() { + return new String[]{JfrEvent.JavaMonitorInflate.getName()}; + } + + @Override + public void validateEvents() throws Throwable { + boolean foundCauseEnter = false; + for (RecordedEvent event : getEvents()) { + String eventThread = event. getValue("eventThread").getJavaName(); + String monitorClass = event. getValue("monitorClass").getName(); + String cause = event.getValue("cause"); + if (monitorClass.equals(EnterHelper.class.getName()) && + cause.equals(MonitorInflationCause.MONITOR_ENTER.getText()) && + (eventThread.equals(firstThread.getName()) || eventThread.equals(secondThread.getName()))) { + foundCauseEnter = true; + } + } + assertTrue("Expected monitor inflate event not found.", foundCauseEnter); + } + + @Test + public void test() throws Exception { + Runnable first = () -> { + try { + ENTER_HELPER.doWork(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + + Runnable second = () -> { + try { + EnterHelper.passedCheckpoint = true; + ENTER_HELPER.doWork(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }; + + firstThread = new Thread(first); + secondThread = new Thread(second); + + /* Generate event with "Monitor Enter" cause. */ + firstThread.start(); + + firstThread.join(); + secondThread.join(); + } + + private static class EnterHelper { + static volatile boolean passedCheckpoint = false; + + synchronized void doWork() throws InterruptedException { + if (Thread.currentThread().equals(secondThread)) { + /* + * The second thread only needs to enter the critical section but doesn't need to do + * work. + */ + return; + } + // ensure ordering of critical section entry + secondThread.start(); + + // spin until second thread blocks + while (!secondThread.getState().equals(Thread.State.BLOCKED) || !passedCheckpoint) { + Thread.sleep(10); + } + Thread.sleep(60); + } + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java index fbcac8ec2fa8..85d4cb520a18 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/JfrFileParser.java @@ -32,10 +32,11 @@ import java.io.IOException; import java.util.HashMap; +import org.junit.Assert; + import com.oracle.svm.core.jfr.JfrChunkWriter; import com.oracle.svm.core.jfr.JfrTicks; import com.oracle.svm.core.jfr.JfrType; - import com.oracle.svm.test.jfr.utils.poolparsers.ClassConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.ClassLoaderConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.ConstantPoolParser; @@ -44,6 +45,7 @@ import com.oracle.svm.test.jfr.utils.poolparsers.GCNameConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.MethodConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.ModuleConstantPoolParser; +import com.oracle.svm.test.jfr.utils.poolparsers.MonitorInflationCauseConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.PackageConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.StacktraceConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.SymbolConstantPoolParser; @@ -51,8 +53,8 @@ import com.oracle.svm.test.jfr.utils.poolparsers.ThreadGroupConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.ThreadStateConstantPoolParser; import com.oracle.svm.test.jfr.utils.poolparsers.VMOperationConstantPoolParser; + import jdk.jfr.Recording; -import org.junit.Assert; public class JfrFileParser { @@ -78,6 +80,7 @@ public class JfrFileParser { supportedConstantPools.put(JfrType.GCName.getId(), new GCNameConstantPoolParser()); supportedConstantPools.put(JfrType.GCCause.getId(), new GCCauseConstantPoolParser()); supportedConstantPools.put(JfrType.VMOperation.getId(), new VMOperationConstantPoolParser()); + supportedConstantPools.put(JfrType.MonitorInflationCause.getId(), new MonitorInflationCauseConstantPoolParser()); } public static HashMap getSupportedConstantPools() { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/MonitorInflationCauseConstantPoolParser.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/MonitorInflationCauseConstantPoolParser.java new file mode 100644 index 000000000000..75777994b4be --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/jfr/utils/poolparsers/MonitorInflationCauseConstantPoolParser.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021, Red Hat Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.test.jfr.utils.poolparsers; + +import java.io.IOException; + +import org.junit.Assert; + +import com.oracle.svm.test.jfr.utils.RecordingInput; + +public class MonitorInflationCauseConstantPoolParser extends ConstantPoolParser { + + @Override + public void parse(RecordingInput input) throws IOException { + int count = input.readInt(); + for (int i = 0; i < count; i++) { + addFoundId(input.readInt()); + Assert.assertFalse("Inflate cause is empty!", input.readUTF().isEmpty()); + } + } +}