Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/hotspot/share/include/jmm.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -80,7 +81,8 @@ typedef enum {
JMM_COMPILE_TOTAL_TIME_MS = 8, /* Total accumulated time spent in compilation */
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_GC_CPU_TIME = 11, /* Total accumulated GC CPU time */
JMM_JVM_UPTIME_MS = 12, /* The JVM uptime in milliseconds */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks a bit odd to me to change the existing define of UPTIME here.
OK it is not a public interface used between different versions, and people should not be mixing up jmm.h and management implementations... But usually we would just add the new definition? (items below are not strictly grouped by name) Maybe this is just a nit, and only makes cross-version comparisons easier.

Looks good overall.


JMM_INTERNAL_ATTRIBUTE_INDEX = 100,
JMM_CLASS_LOADED_BYTES = 101, /* Number of bytes loaded instance classes */
Expand Down
1 change: 1 addition & 0 deletions src/hotspot/share/services/cpuTimeUsage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
19 changes: 19 additions & 0 deletions src/hotspot/share/services/management.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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:
Expand All @@ -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_GC_CPU_TIME:
return get_gc_cpu_time();

case JMM_COMPILE_TOTAL_TIME_MS:
return Management::ticks_to_ms(CompileBroker::total_compilation_ticks());

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -267,6 +267,24 @@ public interface MemoryMXBean extends PlatformManagedObject {
*/
public MemoryUsage getNonHeapMemoryUsage();

/**
* Returns the CPU time used by all garbage collection threads.
*
* <p> This include time since genesis, so the value can be
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “so the” is not too obvious to me. Maybe simplify it a bit “This includes time since genesis and counts activities even before the first collection cycle.”?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ecki for pointing that out :) I make a simplification whenever the CSR discussion (https://bugs.openjdk.org/browse/JDK-8368529) have reached a consensus where this method should live.

* non-zero even if no garbage collection cycles occured. In
* general this includes time for all driver threads,
* workers, VM operations on the VM thread and the string
* deduplication thread (if enabled). This method returns
* {@code -1} if the platform does not support this operation
* or if called during shutdown.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we explicitly state here that this is concurrent time as well as worker time spent during pauses but not accumulated pause times?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just want to ensure that I read this correct, please correct any mistake below:

  • concurrent time = CPU time
  • accumulate times = wall-clock time

Is your suggestion that we should clarify that the method may only account for CPU time and not wall-clock time during pauses?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s mostly about making clear it’s Not a measurement for pause time (no matter if wall clock or per thread), dont know how to best Formulate it. I think it’s needed sind typically pause times have been associated with GRC thread Timings. Maybe something like „the accounted CPU time can be spent concurrently with application threads or during pauses“ or something Like that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the CSR discussion I proposed changing the method name to getTotalGcCpuTime() as @kevinjwalls raised concerns about conflating it with per generation CPU timings. Would the renaming to getTotalGcCpuTime() also solve your concern here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, genesis (Universe::genesis()?), is a VM detail that may be unclear. Do we mean:
"
Returns the CPU time used by all garbage collection threads.

This includes time for all driver threads, workers, VM operations
on the VM thread, and the string deduplication thread (if enabled).
Therefore the value can be non-zero even if no garbage collection cycles have occurred.

This method returns {@code -1} if the platform does not support this operation, or if called during shutdown.
"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant genesis in the literal sense i.e. since thread creation not relating to Universe::genesis. It may be non-zero since each there may do some initialization work and thus CPU time would be non-zero. However given @AlanBateman comment about being HotSpot VM specific should we avoid talking about these specific details here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"CPU time used by all garbage collection threads" or even "Accumulated CPU time..." would seem clear that we mean these threads, in this process. We can leave genesis out of it, whether it meant start of process or dawn of time. 8-)

Yes, might be better as just, "This may include some startup work and overhead, therefore the value can be non-zero even...".

*
* @return the total CPU time for all garbage collection
* threads in nanoseconds.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure did I miss the discussion, other methods like getTotalCompilationTime() return millis, is it ok or required to use new units here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is indeed unfortunate that methods mix units but we are complementing, OperatingSystemMXBean.getProcessCpuTime() which returns nanoseconds.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s in Both names - especially with the Description - understandable what it means, I was just afraid that people could confuse concepts. Maybe it doesn’t really matter. I think in the gc specific ones the colateral doc was a bit more specific about concurrent times and pauses, in the past.

*
* @since 26
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest API docs is good, thanks for accepting all the comments/suggestions to get this right.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should say thank you all for the attention to detail :-) The API docs is indeed much better than the original suggestion.

*/
public long getGcCpuTime();

/**
* Tests if verbose output for the memory system is enabled.
*
Expand Down Expand Up @@ -302,5 +320,4 @@ public interface MemoryMXBean extends PlatformManagedObject {
* @see java.lang.System#gc()
*/
public void gc();

}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ public void gc() {
Runtime.getRuntime().gc();
}

public long getGcCpuTime() {
return jvm.getGcCpuTime();
}

// Need to make a VM call to get coherent value
public MemoryUsage getHeapMemoryUsage() {
return getMemoryUsage0(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public interface VMManagement {
public boolean getVerboseClass();

// Memory Subsystem
public long getGcCpuTime();
public boolean getVerboseGC();

// Runtime Subsystem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ public int getLoadedClassCount() {
public native boolean getVerboseClass();

// Memory Subsystem
public native long getGcCpuTime();
public native boolean getVerboseGC();

// Runtime Subsystem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ Java_sun_management_VMManagementImpl_getUnloadedClassCount
return count;
}

JNIEXPORT jlong JNICALL
Java_sun_management_VMManagementImpl_getGcCpuTime
(JNIEnv *env, jobject dummy)
{
return jmm_interface->GetLongAttribute(env, NULL, JMM_GC_CPU_TIME);
}

JNIEXPORT jboolean JNICALL
Java_sun_management_VMManagementImpl_getVerboseGC
(JNIEnv *env, jobject dummy)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ public int getObjectPendingFinalizationCount() {
return getIntAttribute(OBJECT_PENDING_FINALIZATION_COUNT);
}

public long getGcCpuTime() {
throw new UnsupportedOperationException("This method is not supported");
}

public boolean isVerbose() {
return getBooleanAttribute(VERBOSE);
}
Expand Down
83 changes: 83 additions & 0 deletions test/jdk/java/lang/management/MemoryMXBean/GetGcCpuTime.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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
* @bug 8368527
* @library /test/lib
* @summary Stress MemoryMXBean.getGcCpuTime during shutdown
*
* @run main/othervm -XX:+UseSerialGC GetGcCpuTime _
* @run main/othervm -XX:+UseParallelGC GetGcCpuTime _
* @run main/othervm -XX:+UseG1GC GetGcCpuTime _
* @run main/othervm -XX:+UseZGC GetGcCpuTime _
*/

import jdk.test.lib.process.OutputAnalyzer;
import static jdk.test.lib.process.ProcessTools.createTestJavaProcessBuilder;
import static jdk.test.lib.process.ProcessTools.executeProcess;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.ThreadMXBean;

public class GetGcCpuTime {
static final ThreadMXBean mxThreadBean = ManagementFactory.getThreadMXBean();
static final MemoryMXBean mxMemoryBean = ManagementFactory.getMemoryMXBean();

public static void main(String[] args) throws Exception {
if (args.length > 0) {
ProcessBuilder pb = createTestJavaProcessBuilder("GetGcCpuTime");
OutputAnalyzer output = executeProcess(pb);
output.shouldNotContain("GC CPU time should");
output.shouldHaveExitValue(0);
return;
}

try {
if (!mxThreadBean.isThreadCpuTimeEnabled()) {
return;
}
} catch (UnsupportedOperationException e) {
if (mxMemoryBean.getGcCpuTime() != -1) {
throw new Error("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.getGcCpuTime();
if (gcCpuTimeFromThread < -1) {
throw new Error("GC CPU time should never be less than -1 but was " + gcCpuTimeFromThread);
}
}
});
t.start();
}

System.exit(0);
}
}
2 changes: 2 additions & 0 deletions test/jdk/javax/management/mxbean/MXBeanInteropTest1.java
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,8 @@ private final int doMemoryMXBeanTest(MBeanServerConnection mbsc) {
+ memory.getNonHeapMemoryUsage());
System.out.println("getObjectPendingFinalizationCount\t\t"
+ memory.getObjectPendingFinalizationCount());
System.out.println("getGcCpuTime\t\t"
+ memory.getGcCpuTime());
System.out.println("isVerbose\t\t"
+ memory.isVerbose());

Expand Down