From 812add27abdc70bc52ca105bc9430494a6491ecd Mon Sep 17 00:00:00 2001 From: Jonas Norlinder Date: Mon, 17 Nov 2025 10:42:02 +0000 Subject: [PATCH] 8368527: JMX: Add an MXBeans method to query GC CPU time Reviewed-by: phh, kevinw --- src/hotspot/share/include/jmm.h | 4 +- src/hotspot/share/services/cpuTimeUsage.cpp | 1 + src/hotspot/share/services/management.cpp | 19 +++ .../java/lang/management/MemoryMXBean.java | 40 ++++++- .../classes/sun/management/MemoryImpl.java | 4 + .../classes/sun/management/VMManagement.java | 1 + .../sun/management/VMManagementImpl.java | 1 + .../native/libmanagement/VMManagementImpl.c | 7 ++ .../share/server/ServerMemoryMXBean.java | 4 + ...StressGetTotalGcCpuTimeDuringShutdown.java | 112 ++++++++++++++++++ .../MemoryMXBean/TestGetTotalGcCpuTime.java | 112 ++++++++++++++++++ .../management/mxbean/MXBeanInteropTest1.java | 2 + 12 files changed, 304 insertions(+), 3 deletions(-) create mode 100644 test/jdk/java/lang/management/MemoryMXBean/StressGetTotalGcCpuTimeDuringShutdown.java create mode 100644 test/jdk/java/lang/management/MemoryMXBean/TestGetTotalGcCpuTime.java diff --git a/src/hotspot/share/include/jmm.h b/src/hotspot/share/include/jmm.h index ba7ed3bbca5..ee1462fe5a8 100644 --- a/src/hotspot/share/include/jmm.h +++ b/src/hotspot/share/include/jmm.h @@ -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 */ diff --git a/src/hotspot/share/services/cpuTimeUsage.cpp b/src/hotspot/share/services/cpuTimeUsage.cpp index 27b5e90fbaf..0c7ecfdb655 100644 --- a/src/hotspot/share/services/cpuTimeUsage.cpp +++ b/src/hotspot/share/services/cpuTimeUsage.cpp @@ -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(); diff --git a/src/hotspot/share/services/management.cpp b/src/hotspot/share/services/management.cpp index cfe13d0c8f1..cc26e2e1352 100644 --- a/src/hotspot/share/services/management.cpp +++ b/src/hotspot/share/services/management.cpp @@ -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()); diff --git a/src/java.management/share/classes/java/lang/management/MemoryMXBean.java b/src/java.management/share/classes/java/lang/management/MemoryMXBean.java index 2c982e1ce9c..1211d8d1c4d 100644 --- a/src/java.management/share/classes/java/lang/management/MemoryMXBean.java +++ b/src/java.management/share/classes/java/lang/management/MemoryMXBean.java @@ -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). + * + *

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(); - } diff --git a/src/java.management/share/classes/sun/management/MemoryImpl.java b/src/java.management/share/classes/sun/management/MemoryImpl.java index e1f68ce3711..3e27ba2e5bb 100644 --- a/src/java.management/share/classes/sun/management/MemoryImpl.java +++ b/src/java.management/share/classes/sun/management/MemoryImpl.java @@ -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); diff --git a/src/java.management/share/classes/sun/management/VMManagement.java b/src/java.management/share/classes/sun/management/VMManagement.java index f4445f0225a..3e227225ccd 100644 --- a/src/java.management/share/classes/sun/management/VMManagement.java +++ b/src/java.management/share/classes/sun/management/VMManagement.java @@ -55,6 +55,7 @@ public interface VMManagement { public boolean getVerboseClass(); // Memory Subsystem + public long getTotalGcCpuTime(); public boolean getVerboseGC(); // Runtime Subsystem diff --git a/src/java.management/share/classes/sun/management/VMManagementImpl.java b/src/java.management/share/classes/sun/management/VMManagementImpl.java index 041f09547d2..b8c7a2921d4 100644 --- a/src/java.management/share/classes/sun/management/VMManagementImpl.java +++ b/src/java.management/share/classes/sun/management/VMManagementImpl.java @@ -128,6 +128,7 @@ class VMManagementImpl implements VMManagement { public native boolean getVerboseClass(); // Memory Subsystem + public native long getTotalGcCpuTime(); public native boolean getVerboseGC(); // Runtime Subsystem diff --git a/src/java.management/share/native/libmanagement/VMManagementImpl.c b/src/java.management/share/native/libmanagement/VMManagementImpl.c index f1a566676dc..d5141c71ee7 100644 --- a/src/java.management/share/native/libmanagement/VMManagementImpl.c +++ b/src/java.management/share/native/libmanagement/VMManagementImpl.c @@ -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) diff --git a/test/hotspot/jtreg/vmTestbase/nsk/monitoring/share/server/ServerMemoryMXBean.java b/test/hotspot/jtreg/vmTestbase/nsk/monitoring/share/server/ServerMemoryMXBean.java index 0c536fcd123..31050b6b8bb 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/monitoring/share/server/ServerMemoryMXBean.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/monitoring/share/server/ServerMemoryMXBean.java @@ -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); } diff --git a/test/jdk/java/lang/management/MemoryMXBean/StressGetTotalGcCpuTimeDuringShutdown.java b/test/jdk/java/lang/management/MemoryMXBean/StressGetTotalGcCpuTimeDuringShutdown.java new file mode 100644 index 00000000000..21a8b18ffd7 --- /dev/null +++ b/test/jdk/java/lang/management/MemoryMXBean/StressGetTotalGcCpuTimeDuringShutdown.java @@ -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(); + } + } +} diff --git a/test/jdk/java/lang/management/MemoryMXBean/TestGetTotalGcCpuTime.java b/test/jdk/java/lang/management/MemoryMXBean/TestGetTotalGcCpuTime.java new file mode 100644 index 00000000000..d8a760027a7 --- /dev/null +++ b/test/jdk/java/lang/management/MemoryMXBean/TestGetTotalGcCpuTime.java @@ -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"); + } + } + } +} diff --git a/test/jdk/javax/management/mxbean/MXBeanInteropTest1.java b/test/jdk/javax/management/mxbean/MXBeanInteropTest1.java index 500fef1c2f3..011bdb144fb 100644 --- a/test/jdk/javax/management/mxbean/MXBeanInteropTest1.java +++ b/test/jdk/javax/management/mxbean/MXBeanInteropTest1.java @@ -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());