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";