Skip to content
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

8351142: Add JFR monitor deflation and statistics events #23900

Closed
wants to merge 13 commits into from
9 changes: 9 additions & 0 deletions src/hotspot/share/jfr/metadata/metadata.xml
Original file line number Diff line number Diff line change
@@ -116,6 +116,15 @@
<Field type="InflateCause" name="cause" label="Monitor Inflation Cause" description="Cause of inflation" />
</Event>

<Event name="JavaMonitorDeflate" category="Java Application" label="Java Monitor Deflated">
<Field type="Class" name="monitorClass" label="Monitor Class" />
<Field type="ulong" contentType="address" name="address" label="Monitor Address" relation="JavaMonitorAddress" />
</Event>

<Event name="JavaMonitorStatistics" category="Java Application, Statistics" label="Java Monitor Statistics" period="everyChunk">
<Field type="ulong" name="count" label="Monitors in Use" description="Current number of in-use monitors" />
</Event>

<Event name="SyncOnValueBasedClass" category="Java Virtual Machine, Diagnostics" label="Value Based Class Synchronization" thread="true" stackTrace="true" startTime="false" experimental="true">
<Field type="Class" name="valueBasedClass" label="Value Based Class" />
</Event>
6 changes: 6 additions & 0 deletions src/hotspot/share/jfr/periodic/jfrPeriodic.cpp
Original file line number Diff line number Diff line change
@@ -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();
}
6 changes: 5 additions & 1 deletion src/hotspot/share/runtime/lightweightSynchronizer.cpp
Original file line number Diff line number Diff line change
@@ -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();
26 changes: 19 additions & 7 deletions src/hotspot/share/runtime/objectMonitor.cpp
Original file line number Diff line number Diff line change
@@ -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);
4 changes: 4 additions & 0 deletions src/hotspot/share/runtime/objectMonitor.hpp
Original file line number Diff line number Diff line change
@@ -463,6 +463,10 @@ class ObjectMonitor : public CHeapObj<mtObjectMonitor> {
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
7 changes: 7 additions & 0 deletions src/hotspot/share/runtime/objectMonitor.inline.hpp
Original file line number Diff line number Diff line change
@@ -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
18 changes: 15 additions & 3 deletions src/hotspot/share/runtime/synchronizer.cpp
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 2 additions & 0 deletions src/hotspot/share/runtime/synchronizer.hpp
Original file line number Diff line number Diff line change
@@ -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();
10 changes: 10 additions & 0 deletions src/jdk.jfr/share/conf/jfr/default.jfc
Original file line number Diff line number Diff line change
@@ -101,6 +101,16 @@
<setting name="threshold">0 ms</setting>
</event>

<event name="jdk.JavaMonitorDeflate">
<setting name="enabled">false</setting>
<setting name="threshold">0 ms</setting>
</event>

<event name="jdk.JavaMonitorStatistics">
<setting name="enabled">true</setting>
<setting name="period">everyChunk</setting>
</event>

<event name="jdk.SyncOnValueBasedClass">
<setting name="enabled">true</setting>
<setting name="stackTrace">true</setting>
10 changes: 10 additions & 0 deletions src/jdk.jfr/share/conf/jfr/profile.jfc
Original file line number Diff line number Diff line change
@@ -101,6 +101,16 @@
<setting name="threshold">0 ms</setting>
</event>

<event name="jdk.JavaMonitorDeflate">
<setting name="enabled">false</setting>
<setting name="threshold">0 ms</setting>
</event>

<event name="jdk.JavaMonitorStatistics">
<setting name="enabled">true</setting>
<setting name="period">everyChunk</setting>
</event>

<event name="jdk.SyncOnValueBasedClass">
<setting name="enabled">true</setting>
<setting name="stackTrace">true</setting>
90 changes: 90 additions & 0 deletions test/jdk/jdk/jfr/event/runtime/TestJavaMonitorDeflateEvent.java
Original file line number Diff line number Diff line change
@@ -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<RecordedEvent> 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);
}
}
}
}
101 changes: 101 additions & 0 deletions test/jdk/jdk/jfr/event/runtime/TestJavaMonitorStatisticsEvent.java
Original file line number Diff line number Diff line change
@@ -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<RecordedEvent> 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);
}
}
}
2 changes: 2 additions & 0 deletions test/lib/jdk/test/lib/jfr/EventNames.java
Original file line number Diff line number Diff line change
@@ -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";