diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml
index a4b9bc7890007..3151ad72af187 100644
--- a/src/hotspot/share/jfr/metadata/metadata.xml
+++ b/src/hotspot/share/jfr/metadata/metadata.xml
@@ -116,6 +116,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp
index f22098bb52e18..91a696c6aa56b 100644
--- a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp
+++ b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp
@@ -739,3 +739,9 @@ TRACE_REQUEST_FUNC(NativeMemoryUsage) {
TRACE_REQUEST_FUNC(NativeMemoryUsageTotal) {
JfrNativeMemoryEvent::send_total_event(timestamp());
}
+
+TRACE_REQUEST_FUNC(JavaMonitorStatistics) {
+ EventJavaMonitorStatistics event;
+ event.set_count(ObjectSynchronizer::in_use_list_count());
+ event.commit();
+}
diff --git a/src/hotspot/share/runtime/lightweightSynchronizer.cpp b/src/hotspot/share/runtime/lightweightSynchronizer.cpp
index 55d4a0c30f3d5..44d0cb32b5693 100644
--- a/src/hotspot/share/runtime/lightweightSynchronizer.cpp
+++ b/src/hotspot/share/runtime/lightweightSynchronizer.cpp
@@ -369,7 +369,11 @@ static void post_monitor_inflate_event(EventJavaMonitorInflate* event,
const oop obj,
ObjectSynchronizer::InflateCause cause) {
assert(event != nullptr, "invariant");
- event->set_monitorClass(obj->klass());
+ const Klass* monitor_klass = obj->klass();
+ if (ObjectMonitor::is_jfr_excluded(monitor_klass)) {
+ return;
+ }
+ event->set_monitorClass(monitor_klass);
event->set_address((uintptr_t)(void*)obj);
event->set_cause((u1)cause);
event->commit();
diff --git a/src/hotspot/share/runtime/objectMonitor.cpp b/src/hotspot/share/runtime/objectMonitor.cpp
index 7f09a1cea3221..636172f371dbd 100644
--- a/src/hotspot/share/runtime/objectMonitor.cpp
+++ b/src/hotspot/share/runtime/objectMonitor.cpp
@@ -734,6 +734,18 @@ bool ObjectMonitor::try_lock_or_add_to_entry_list(JavaThread* current, ObjectWai
}
}
+static void post_monitor_deflate_event(EventJavaMonitorDeflate* event,
+ const oop obj) {
+ assert(event != nullptr, "invariant");
+ const Klass* monitor_klass = obj->klass();
+ if (ObjectMonitor::is_jfr_excluded(monitor_klass)) {
+ return;
+ }
+ event->set_monitorClass(monitor_klass);
+ event->set_address((uintptr_t)(void*)obj);
+ event->commit();
+}
+
// Deflate the specified ObjectMonitor if not in-use. Returns true if it
// was deflated and false otherwise.
//
@@ -753,6 +765,8 @@ bool ObjectMonitor::deflate_monitor(Thread* current) {
return false;
}
+ EventJavaMonitorDeflate event;
+
const oop obj = object_peek();
if (obj == nullptr) {
@@ -826,6 +840,10 @@ bool ObjectMonitor::deflate_monitor(Thread* current) {
install_displaced_markword_in_object(obj);
}
+ if (event.should_commit()) {
+ post_monitor_deflate_event(&event, obj);
+ }
+
// We leave owner == DEFLATER_MARKER and contentions < 0
// to force any racing threads to retry.
return true; // Success, ObjectMonitor has been deflated.
@@ -1628,12 +1646,6 @@ bool ObjectMonitor::check_owner(TRAPS) {
"current thread is not owner", false);
}
-static inline bool is_excluded(const Klass* monitor_klass) {
- assert(monitor_klass != nullptr, "invariant");
- NOT_JFR_RETURN_(false);
- JFR_ONLY(return vmSymbols::jdk_jfr_internal_management_HiddenWait() == monitor_klass->name();)
-}
-
static void post_monitor_wait_event(EventJavaMonitorWait* event,
ObjectMonitor* monitor,
uint64_t notifier_tid,
@@ -1642,7 +1654,7 @@ static void post_monitor_wait_event(EventJavaMonitorWait* event,
assert(event != nullptr, "invariant");
assert(monitor != nullptr, "invariant");
const Klass* monitor_klass = monitor->object()->klass();
- if (is_excluded(monitor_klass)) {
+ if (ObjectMonitor::is_jfr_excluded(monitor_klass)) {
return;
}
event->set_monitorClass(monitor_klass);
diff --git a/src/hotspot/share/runtime/objectMonitor.hpp b/src/hotspot/share/runtime/objectMonitor.hpp
index b8409f2a05447..e3af2422eb703 100644
--- a/src/hotspot/share/runtime/objectMonitor.hpp
+++ b/src/hotspot/share/runtime/objectMonitor.hpp
@@ -463,6 +463,10 @@ class ObjectMonitor : public CHeapObj {
bool deflate_monitor(Thread* current);
private:
void install_displaced_markword_in_object(const oop obj);
+
+ // JFR support
+public:
+ static bool is_jfr_excluded(const Klass* monitor_klass);
};
// RAII object to ensure that ObjectMonitor::is_being_async_deflated() is
diff --git a/src/hotspot/share/runtime/objectMonitor.inline.hpp b/src/hotspot/share/runtime/objectMonitor.inline.hpp
index d8d7b3f763328..e5cad9b365a03 100644
--- a/src/hotspot/share/runtime/objectMonitor.inline.hpp
+++ b/src/hotspot/share/runtime/objectMonitor.inline.hpp
@@ -27,6 +27,7 @@
#include "runtime/objectMonitor.hpp"
+#include "classfile/vmSymbols.hpp"
#include "logging/log.hpp"
#include "oops/access.inline.hpp"
#include "oops/markWord.hpp"
@@ -286,4 +287,10 @@ inline bool ObjectMonitor::object_refers_to(oop obj) const {
return _object.peek() == obj;
}
+inline bool ObjectMonitor::is_jfr_excluded(const Klass* monitor_klass) {
+ assert(monitor_klass != nullptr, "invariant");
+ NOT_JFR_RETURN_(false);
+ JFR_ONLY(return vmSymbols::jdk_jfr_internal_management_HiddenWait() == monitor_klass->name();)
+}
+
#endif // SHARE_RUNTIME_OBJECTMONITOR_INLINE_HPP
diff --git a/src/hotspot/share/runtime/synchronizer.cpp b/src/hotspot/share/runtime/synchronizer.cpp
index e3faba0754d86..2a97dba7119de 100644
--- a/src/hotspot/share/runtime/synchronizer.cpp
+++ b/src/hotspot/share/runtime/synchronizer.cpp
@@ -1312,6 +1312,14 @@ static bool monitors_used_above_threshold(MonitorList* list) {
return false;
}
+size_t ObjectSynchronizer::in_use_list_count() {
+ return _in_use_list.count();
+}
+
+size_t ObjectSynchronizer::in_use_list_max() {
+ return _in_use_list.max();
+}
+
size_t ObjectSynchronizer::in_use_list_ceiling() {
return _in_use_list_ceiling;
}
@@ -1419,7 +1427,11 @@ static void post_monitor_inflate_event(EventJavaMonitorInflate* event,
const oop obj,
ObjectSynchronizer::InflateCause cause) {
assert(event != nullptr, "invariant");
- event->set_monitorClass(obj->klass());
+ const Klass* monitor_klass = obj->klass();
+ if (ObjectMonitor::is_jfr_excluded(monitor_klass)) {
+ return;
+ }
+ event->set_monitorClass(monitor_klass);
event->set_address((uintptr_t)(void*)obj);
event->set_cause((u1)cause);
event->commit();
@@ -1710,8 +1722,8 @@ class ObjectMonitorDeflationLogging: public StackObj {
elapsedTimer _timer;
size_t ceiling() const { return ObjectSynchronizer::in_use_list_ceiling(); }
- size_t count() const { return ObjectSynchronizer::_in_use_list.count(); }
- size_t max() const { return ObjectSynchronizer::_in_use_list.max(); }
+ size_t count() const { return ObjectSynchronizer::in_use_list_count(); }
+ size_t max() const { return ObjectSynchronizer::in_use_list_max(); }
public:
ObjectMonitorDeflationLogging()
diff --git a/src/hotspot/share/runtime/synchronizer.hpp b/src/hotspot/share/runtime/synchronizer.hpp
index 5d5a123a2b135..089a432a8b5eb 100644
--- a/src/hotspot/share/runtime/synchronizer.hpp
+++ b/src/hotspot/share/runtime/synchronizer.hpp
@@ -188,6 +188,8 @@ class ObjectSynchronizer : AllStatic {
// Deflate idle monitors:
static size_t deflate_monitor_list(ObjectMonitorDeflationSafepointer* safepointer);
+ static size_t in_use_list_count();
+ static size_t in_use_list_max();
static size_t in_use_list_ceiling();
static void dec_in_use_list_ceiling();
static void inc_in_use_list_ceiling();
diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc
index c9bdb0828a652..2335911926e6a 100644
--- a/src/jdk.jfr/share/conf/jfr/default.jfc
+++ b/src/jdk.jfr/share/conf/jfr/default.jfc
@@ -101,6 +101,16 @@
0 ms
+
+ false
+ 0 ms
+
+
+
+ true
+ everyChunk
+
+
true
true
diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc
index 44b11e4d15082..1af316bfe81ea 100644
--- a/src/jdk.jfr/share/conf/jfr/profile.jfc
+++ b/src/jdk.jfr/share/conf/jfr/profile.jfc
@@ -101,6 +101,16 @@
0 ms
+
+ false
+ 0 ms
+
+
+
+ true
+ everyChunk
+
+
true
true
diff --git a/test/jdk/jdk/jfr/event/runtime/TestJavaMonitorDeflateEvent.java b/test/jdk/jdk/jfr/event/runtime/TestJavaMonitorDeflateEvent.java
new file mode 100644
index 0000000000000..2c10911179b3d
--- /dev/null
+++ b/test/jdk/jdk/jfr/event/runtime/TestJavaMonitorDeflateEvent.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2016, 2025, Oracle and/or its affiliates. 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.
+ *
+ * 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 jdk.jfr.event.runtime;
+
+import static jdk.test.lib.Asserts.assertFalse;
+
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.RecordingStream;
+import jdk.jfr.consumer.RecordedClass;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.test.lib.jfr.EventNames;
+import jdk.test.lib.jfr.Events;
+import jdk.test.lib.thread.TestThread;
+import jdk.test.lib.thread.XRun;
+
+/**
+ * @test TestJavaMonitorDeflateEvent
+ * @requires vm.flagless
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:GuaranteedAsyncDeflationInterval=100 jdk.jfr.event.runtime.TestJavaMonitorDeflateEvent
+ */
+public class TestJavaMonitorDeflateEvent {
+
+ private static final String FIELD_KLASS_NAME = "monitorClass.name";
+ private static final String FIELD_ADDRESS = "address";
+
+ private static final String EVENT_NAME = EventNames.JavaMonitorDeflate;
+
+ static class Lock {
+ }
+
+ public static void main(String[] args) throws Exception {
+ final Lock lock = new Lock();
+ final String lockClassName = lock.getClass().getName().replace('.', '/');
+
+ List events = new CopyOnWriteArrayList<>();
+ try (RecordingStream rs = new RecordingStream()) {
+ rs.enable(EVENT_NAME).withoutThreshold();
+ rs.onEvent(EVENT_NAME, e -> {
+ Object clazz = e.getValue(FIELD_KLASS_NAME);
+ if (clazz.equals(lockClassName)) {
+ events.add(e);
+ rs.close();
+ }
+ });
+ rs.startAsync();
+
+ synchronized (lock) {
+ // Causes lock inflation.
+ lock.wait(1);
+ }
+
+ // Wait for deflater thread to act.
+ rs.awaitTermination();
+
+ System.out.println(events);
+ assertFalse(events.isEmpty());
+ for (RecordedEvent ev : events) {
+ Events.assertField(ev, FIELD_ADDRESS).notEqual(0L);
+ }
+ }
+ }
+}
diff --git a/test/jdk/jdk/jfr/event/runtime/TestJavaMonitorStatisticsEvent.java b/test/jdk/jdk/jfr/event/runtime/TestJavaMonitorStatisticsEvent.java
new file mode 100644
index 0000000000000..baf549bead081
--- /dev/null
+++ b/test/jdk/jdk/jfr/event/runtime/TestJavaMonitorStatisticsEvent.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2016, 2025, Oracle and/or its affiliates. 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.
+ *
+ * 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 jdk.jfr.event.runtime;
+
+import static jdk.test.lib.Asserts.assertFalse;
+import static jdk.test.lib.Asserts.assertTrue;
+
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.RecordingStream;
+import jdk.jfr.consumer.RecordedClass;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.test.lib.jfr.EventNames;
+import jdk.test.lib.jfr.Events;
+import jdk.test.lib.thread.TestThread;
+import jdk.test.lib.thread.XRun;
+
+/**
+ * @test TestJavaMonitorStatisticsEvent
+ * @requires vm.flagless
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.event.runtime.TestJavaMonitorStatisticsEvent
+ */
+public class TestJavaMonitorStatisticsEvent {
+
+ private static final String FIELD_COUNT = "count";
+
+ private static final String EVENT_NAME = EventNames.JavaMonitorStatistics;
+ private static final int NUM_LOCKS = 512;
+
+ static class Lock {
+ }
+
+ static final Lock[] LOCKS = new Lock[NUM_LOCKS];
+
+ static void lockNext(int idx, Runnable action) throws InterruptedException {
+ if (idx >= NUM_LOCKS) {
+ action.run();
+ return;
+ }
+ synchronized (LOCKS[idx]) {
+ LOCKS[idx].wait(1);
+ lockNext(idx + 1, action);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ List events = new CopyOnWriteArrayList<>();
+ try (RecordingStream rs = new RecordingStream()) {
+ rs.enable(EVENT_NAME).with("period", "everyChunk");
+ rs.onEvent(EVENT_NAME, e -> events.add(e));
+ rs.startAsync();
+
+ // Recursively lock all, causing NUM_LOCKS monitors to exist.
+ // Stop the recording when holding all the locks, so that we
+ // get at least one event with NUM_LOCKS max.
+ for (int c = 0; c < NUM_LOCKS; c++) {
+ LOCKS[c] = new Lock();
+ }
+ lockNext(0, () -> rs.stop());
+
+ System.out.println(events);
+ assertFalse(events.isEmpty());
+
+ long globalCount = Long.MIN_VALUE;
+ for (RecordedEvent ev : events) {
+ long evCount = Events.assertField(ev, FIELD_COUNT).getValue();
+ assertTrue(evCount >= 0, "Count should be non-negative: " + evCount);
+ globalCount = Math.max(globalCount, evCount);
+ }
+
+ assertTrue(globalCount >= NUM_LOCKS, "Global count should be at least " + NUM_LOCKS + ": " + globalCount);
+ }
+ }
+}
diff --git a/test/lib/jdk/test/lib/jfr/EventNames.java b/test/lib/jdk/test/lib/jfr/EventNames.java
index 0ef1b5e6d3f92..9aaa231b8a832 100644
--- a/test/lib/jdk/test/lib/jfr/EventNames.java
+++ b/test/lib/jdk/test/lib/jfr/EventNames.java
@@ -59,6 +59,8 @@ public class EventNames {
public static final String JavaMonitorEnter = PREFIX + "JavaMonitorEnter";
public static final String JavaMonitorWait = PREFIX + "JavaMonitorWait";
public static final String JavaMonitorInflate = PREFIX + "JavaMonitorInflate";
+ public static final String JavaMonitorDeflate = PREFIX + "JavaMonitorDeflate";
+ public static final String JavaMonitorStatistics = PREFIX + "JavaMonitorStatistics";
public static final String SyncOnValueBasedClass = PREFIX + "SyncOnValueBasedClass";
public static final String ClassLoad = PREFIX + "ClassLoad";
public static final String ClassDefine = PREFIX + "ClassDefine";