8368527: JMX: Add an MXBeans method to query GC CPU time

Reviewed-by: phh, kevinw
This commit is contained in:
Jonas Norlinder 2025-11-17 10:42:02 +00:00 committed by Kevin Walls
parent 09b25cd0a2
commit 812add27ab
12 changed files with 304 additions and 3 deletions

View File

@ -53,7 +53,8 @@ enum {
JMM_VERSION_2 = 0x20020000, // JDK 10
JMM_VERSION_3 = 0x20030000, // JDK 14
JMM_VERSION_4 = 0x20040000, // JDK 21
JMM_VERSION = JMM_VERSION_4
JMM_VERSION_5 = 0x20050000, // JDK 26
JMM_VERSION = JMM_VERSION_5
};
typedef struct {
@ -81,6 +82,7 @@ typedef enum {
JMM_GC_TIME_MS = 9, /* Total accumulated time spent in collection */
JMM_GC_COUNT = 10, /* Total number of collections */
JMM_JVM_UPTIME_MS = 11, /* The JVM uptime in milliseconds */
JMM_TOTAL_GC_CPU_TIME = 12, /* Total accumulated GC CPU time */
JMM_INTERNAL_ATTRIBUTE_INDEX = 100,
JMM_CLASS_LOADED_BYTES = 101, /* Number of bytes loaded instance classes */

View File

@ -36,6 +36,7 @@
volatile bool CPUTimeUsage::Error::_has_error = false;
static inline jlong thread_cpu_time_or_zero(Thread* thread) {
assert(!Universe::is_shutting_down(), "Should not query during shutdown");
jlong cpu_time = os::thread_cpu_time(thread);
if (cpu_time == -1) {
CPUTimeUsage::Error::mark_error();

View File

@ -54,6 +54,7 @@
#include "runtime/threadSMR.hpp"
#include "runtime/vmOperations.hpp"
#include "services/classLoadingService.hpp"
#include "services/cpuTimeUsage.hpp"
#include "services/diagnosticCommand.hpp"
#include "services/diagnosticFramework.hpp"
#include "services/finalizerService.hpp"
@ -889,6 +890,21 @@ static jint get_num_flags() {
return count;
}
static jlong get_gc_cpu_time() {
if (!os::is_thread_cpu_time_supported()) {
return -1;
}
{
MutexLocker hl(Heap_lock);
if (Universe::heap()->is_shutting_down()) {
return -1;
}
return CPUTimeUsage::GC::total();
}
}
static jlong get_long_attribute(jmmLongAttribute att) {
switch (att) {
case JMM_CLASS_LOADED_COUNT:
@ -915,6 +931,9 @@ static jlong get_long_attribute(jmmLongAttribute att) {
case JMM_JVM_UPTIME_MS:
return Management::ticks_to_ms(os::elapsed_counter());
case JMM_TOTAL_GC_CPU_TIME:
return get_gc_cpu_time();
case JMM_COMPILE_TOTAL_TIME_MS:
return Management::ticks_to_ms(CompileBroker::total_compilation_ticks());

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 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
@ -267,6 +267,43 @@ public interface MemoryMXBean extends PlatformManagedObject {
*/
public MemoryUsage getNonHeapMemoryUsage();
/**
* Returns the approximate accumulated time, in nanoseconds,
* spent in garbage collection (GC).
*
* <p> The time spent in spent in GC is the CPU time used by
* all GC activity, including any overhead, which means the
* result may be non-zero even if no GC has occurred.
*
* This method returns {@code -1} if the platform does
* not support this operation or the information is not
* available.
*
* @apiNote
* May be used in conjunction with {@link jdk.management/com.sun.management.OperatingSystemMXBean#getProcessCpuTime()}
* for calculating the GC's usage of CPU time as a whole.
*
* @implNote The specifics on what constitutes the time spent
* in GC are highly implementation dependent. In the HotSpot
* Virtual Machine, this time includes relevant
* implementation-specific details such as driver threads,
* workers, VM Operations and string deduplication (if
* enabled). Driver threads may be created by a GC to
* orchestrate its work. The return value can be -1 if called
* when measurement is not possible, such as during shutdown.
*
* @implSpec The default implementation returns {@code -1}.
*
* @return the total accumulated CPU time for GC in
* nanoseconds, or {@code -1}.
*
* @since 26
*/
@SuppressWarnings("doclint:reference")
default public long getTotalGcCpuTime() {
return -1;
}
/**
* Tests if verbose output for the memory system is enabled.
*
@ -302,5 +339,4 @@ public interface MemoryMXBean extends PlatformManagedObject {
* @see java.lang.System#gc()
*/
public void gc();
}

View File

@ -67,6 +67,10 @@ class MemoryImpl extends NotificationEmitterSupport
Runtime.getRuntime().gc();
}
public long getTotalGcCpuTime() {
return jvm.getTotalGcCpuTime();
}
// Need to make a VM call to get coherent value
public MemoryUsage getHeapMemoryUsage() {
return getMemoryUsage0(true);

View File

@ -55,6 +55,7 @@ public interface VMManagement {
public boolean getVerboseClass();
// Memory Subsystem
public long getTotalGcCpuTime();
public boolean getVerboseGC();
// Runtime Subsystem

View File

@ -128,6 +128,7 @@ class VMManagementImpl implements VMManagement {
public native boolean getVerboseClass();
// Memory Subsystem
public native long getTotalGcCpuTime();
public native boolean getVerboseGC();
// Runtime Subsystem

View File

@ -121,6 +121,13 @@ Java_sun_management_VMManagementImpl_getUnloadedClassCount
return count;
}
JNIEXPORT jlong JNICALL
Java_sun_management_VMManagementImpl_getTotalGcCpuTime
(JNIEnv *env, jobject dummy)
{
return jmm_interface->GetLongAttribute(env, NULL, JMM_TOTAL_GC_CPU_TIME);
}
JNIEXPORT jboolean JNICALL
Java_sun_management_VMManagementImpl_getVerboseGC
(JNIEnv *env, jobject dummy)

View File

@ -58,6 +58,10 @@ public class ServerMemoryMXBean extends ServerMXBean implements MemoryMXBean {
return getIntAttribute(OBJECT_PENDING_FINALIZATION_COUNT);
}
public long getTotalGcCpuTime() {
throw new UnsupportedOperationException("This method is not supported");
}
public boolean isVerbose() {
return getBooleanAttribute(VERBOSE);
}

View File

@ -0,0 +1,112 @@
/*
* Copyright (c) 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.
*/
/*
* @test id=Epsilon
* @requires vm.gc.Epsilon
* @bug 8368527
* @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown
* @library /test/lib
* @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC StressGetTotalGcCpuTimeDuringShutdown
*/
/*
* @test id=Serial
* @requires vm.gc.Serial
* @bug 8368527
* @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown
* @library /test/lib
* @run main/othervm -XX:+UseSerialGC StressGetTotalGcCpuTimeDuringShutdown
*/
/*
* @test id=Parallel
* @requires vm.gc.Parallel
* @bug 8368527
* @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown
* @library /test/lib
* @run main/othervm -XX:+UseParallelGC StressGetTotalGcCpuTimeDuringShutdown
*/
/*
* @test id=G1
* @requires vm.gc.G1
* @bug 8368527
* @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown
* @library /test/lib
* @run main/othervm -XX:+UseG1GC StressGetTotalGcCpuTimeDuringShutdown
*/
/*
* @test id=ZGC
* @requires vm.gc.Z
* @bug 8368527
* @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown
* @library /test/lib
* @run main/othervm -XX:+UseZGC StressGetTotalGcCpuTimeDuringShutdown
*/
/*
* @test id=Shenandoah
* @requires vm.gc.Shenandoah
* @bug 8368527
* @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown
* @library /test/lib
* @run main/othervm -XX:+UseShenandoahGC StressGetTotalGcCpuTimeDuringShutdown
*/
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.ThreadMXBean;
public class StressGetTotalGcCpuTimeDuringShutdown {
static final ThreadMXBean mxThreadBean = ManagementFactory.getThreadMXBean();
static final MemoryMXBean mxMemoryBean = ManagementFactory.getMemoryMXBean();
public static void main(String[] args) throws Exception {
try {
if (!mxThreadBean.isThreadCpuTimeEnabled()) {
return;
}
} catch (UnsupportedOperationException e) {
if (mxMemoryBean.getTotalGcCpuTime() != -1) {
throw new RuntimeException("GC CPU time should be -1");
}
return;
}
final int numberOfThreads = Runtime.getRuntime().availableProcessors() * 8;
for (int i = 0; i < numberOfThreads; i++) {
Thread t = new Thread(() -> {
while (true) {
long gcCpuTimeFromThread = mxMemoryBean.getTotalGcCpuTime();
if (gcCpuTimeFromThread < -1) {
throw new RuntimeException("GC CPU time should never be less than -1 but was " + gcCpuTimeFromThread);
}
}
});
t.setDaemon(true);
t.start();
}
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright (c) 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.
*/
/*
* @test id=Epsilon
* @requires vm.gc.Epsilon
* @bug 8368527
* @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown
* @library /test/lib
* @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC TestGetTotalGcCpuTime
*/
/*
* @test id=Serial
* @requires vm.gc.Serial
* @bug 8368527
* @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown
* @library /test/lib
* @run main/othervm -XX:+UseSerialGC TestGetTotalGcCpuTime
*/
/*
* @test id=Parallel
* @requires vm.gc.Parallel
* @bug 8368527
* @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown
* @library /test/lib
* @run main/othervm -XX:+UseParallelGC TestGetTotalGcCpuTime
*/
/*
* @test id=G1
* @requires vm.gc.G1
* @bug 8368527
* @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown
* @library /test/lib
* @run main/othervm -XX:+UseG1GC TestGetTotalGcCpuTime
*/
/*
* @test id=ZGC
* @requires vm.gc.Z
* @bug 8368527
* @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown
* @library /test/lib
* @run main/othervm -XX:+UseZGC TestGetTotalGcCpuTime
*/
/*
* @test id=Shenandoah
* @requires vm.gc.Shenandoah
* @bug 8368527
* @summary Stress MemoryMXBean.getTotalGcCpuTime during shutdown
* @library /test/lib
* @run main/othervm -XX:+UseShenandoahGC TestGetTotalGcCpuTime
*/
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.ThreadMXBean;
public class TestGetTotalGcCpuTime {
static final ThreadMXBean mxThreadBean = ManagementFactory.getThreadMXBean();
static final MemoryMXBean mxMemoryBean = ManagementFactory.getMemoryMXBean();
static final boolean usingEpsilonGC = ManagementFactory.getRuntimeMXBean().getInputArguments().stream().anyMatch(p -> p.contains("-XX:+UseEpsilonGC"));
public static void main(String[] args) throws Exception {
try {
if (!mxThreadBean.isThreadCpuTimeEnabled()) {
return;
}
} catch (UnsupportedOperationException e) {
if (mxMemoryBean.getTotalGcCpuTime() != -1) {
throw new RuntimeException("GC CPU time should be -1");
}
return;
}
System.gc();
long gcCpuTimeFromThread = mxMemoryBean.getTotalGcCpuTime();
if (usingEpsilonGC) {
if (gcCpuTimeFromThread != 0) {
throw new RuntimeException("Epsilon GC can't have any GC CPU time by definition");
}
} else {
if (gcCpuTimeFromThread <= 0) {
throw new RuntimeException("Some GC CPU time must have been reported");
}
}
}
}

View File

@ -222,6 +222,8 @@ public class MXBeanInteropTest1 {
+ memory.getNonHeapMemoryUsage());
System.out.println("getObjectPendingFinalizationCount\t\t"
+ memory.getObjectPendingFinalizationCount());
System.out.println("getTotalGcCpuTime\t\t"
+ memory.getTotalGcCpuTime());
System.out.println("isVerbose\t\t"
+ memory.isVerbose());