8304074: [JMX] Add an approximation of total bytes allocated on the Java heap by the JVM

Reviewed-by: dholmes, mchung
This commit is contained in:
Paul Hohensee 2023-05-30 13:44:02 +00:00
parent 15e028530a
commit 3eced01f9e
11 changed files with 253 additions and 79 deletions
src
hotspot/share
java.management/share
classes/sun/management
native/libmanagement
jdk.management/share/classes/com/sun/management
test/jdk/com/sun/management/ThreadMXBean

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2023, 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
@ -52,7 +52,8 @@ enum {
JMM_VERSION_1_2_2 = 0x20010202,
JMM_VERSION_2 = 0x20020000, // JDK 10
JMM_VERSION_3 = 0x20030000, // JDK 14
JMM_VERSION = JMM_VERSION_3
JMM_VERSION_4 = 0x20040000, // JDK 21
JMM_VERSION = JMM_VERSION_4
};
typedef struct {
@ -240,6 +241,8 @@ typedef struct jmmInterface_1_ {
jobject (JNICALL *GetMemoryPoolUsage) (JNIEnv* env, jobject pool);
jobject (JNICALL *GetPeakMemoryPoolUsage) (JNIEnv* env, jobject pool);
jlong (JNICALL *GetTotalThreadAllocatedMemory)
(JNIEnv *env);
jlong (JNICALL *GetOneThreadAllocatedMemory)
(JNIEnv *env,
jlong thread_id);

@ -74,7 +74,7 @@ extern Mutex* G1RareEvent_lock; // Synchronizes (rare) parallel
extern Mutex* G1DetachedRefinementStats_lock; // Lock protecting detached refinement stats
extern Mutex* MarkStackFreeList_lock; // Protects access to the global mark stack free list.
extern Mutex* MarkStackChunkList_lock; // Protects access to the global mark stack chunk list.
extern Mutex* MonitoringSupport_lock; // Protects updates to the serviceability memory pools.
extern Mutex* MonitoringSupport_lock; // Protects updates to the serviceability memory pools and allocated memory high water mark.
extern Monitor* ConcurrentGCBreakpoints_lock; // Protects concurrent GC breakpoint management
extern Mutex* Compile_lock; // a lock held when Compilation is updating code (used to block CodeCache traversal, CHA updates, etc)
extern Monitor* MethodCompileQueue_lock; // a lock held when method compilations are enqueued, dequeued

@ -47,6 +47,7 @@
#include "runtime/interfaceSupport.inline.hpp"
#include "runtime/javaCalls.hpp"
#include "runtime/jniHandles.inline.hpp"
#include "runtime/mutexLocker.hpp"
#include "runtime/notificationThread.hpp"
#include "runtime/os.hpp"
#include "runtime/thread.inline.hpp"
@ -428,8 +429,6 @@ static MemoryPool* get_memory_pool_from_jobject(jobject obj, TRAPS) {
return MemoryService::get_memory_pool(ph);
}
#endif // INCLUDE_MANAGEMENT
static void validate_thread_id_array(typeArrayHandle ids_ah, TRAPS) {
int num_threads = ids_ah->length();
@ -455,8 +454,6 @@ static bool is_platform_thread(JavaThread* jt) {
}
}
#if INCLUDE_MANAGEMENT
static void validate_thread_info_array(objArrayHandle infoArray_h, TRAPS) {
// check if the element of infoArray is of type ThreadInfo class
Klass* threadinfo_klass = Management::java_lang_management_ThreadInfo_klass(CHECK);
@ -2095,7 +2092,41 @@ jlong Management::ticks_to_ms(jlong ticks) {
return (jlong)(((double)ticks / (double)os::elapsed_frequency())
* (double)1000.0);
}
#endif // INCLUDE_MANAGEMENT
// Gets the amount of memory allocated on the Java heap since JVM launch.
JVM_ENTRY(jlong, jmm_GetTotalThreadAllocatedMemory(JNIEnv *env))
// A thread increments exited_allocated_bytes in ThreadService::remove_thread
// only after it removes itself from the threads list, and once a TLH is
// created, no thread it references can remove itself from the threads
// list, so none can update exited_allocated_bytes. We therefore initialize
// result with exited_allocated_bytes after after we create the TLH so that
// the final result can only be short due to (1) threads that start after
// the TLH is created, or (2) terminating threads that escape TLH creation
// and don't update exited_allocated_bytes before we initialize result.
// We keep a high water mark to ensure monotonicity in case threads counted
// on a previous call end up in state (2).
static jlong high_water_result = 0;
JavaThreadIteratorWithHandle jtiwh;
jlong result = ThreadService::exited_allocated_bytes();
for (; JavaThread* thread = jtiwh.next();) {
jlong size = thread->cooked_allocated_bytes();
result += size;
}
{
MutexLocker ml(MonitoringSupport_lock, Mutex::_no_safepoint_check_flag);
if (result < high_water_result) {
// Encountered (2) above, or result wrapped to a negative value. In
// the latter case, it's pegged at the last positive value.
result = high_water_result;
} else {
high_water_result = result;
}
}
return result;
JVM_END
// Gets the amount of memory allocated on the Java heap for a single thread.
// Returns -1 if the thread does not exist or has terminated.
@ -2228,9 +2259,6 @@ JVM_ENTRY(void, jmm_GetThreadCpuTimesWithKind(JNIEnv *env, jlongArray ids,
}
JVM_END
#if INCLUDE_MANAGEMENT
const struct jmmInterface_1_ jmm_interface = {
nullptr,
nullptr,
@ -2241,6 +2269,7 @@ const struct jmmInterface_1_ jmm_interface = {
jmm_GetMemoryManagers,
jmm_GetMemoryPoolUsage,
jmm_GetPeakMemoryPoolUsage,
jmm_GetTotalThreadAllocatedMemory,
jmm_GetOneThreadAllocatedMemory,
jmm_GetThreadAllocatedMemory,
jmm_GetMemoryUsage,

@ -45,6 +45,7 @@
#include "runtime/init.hpp"
#include "runtime/javaThread.inline.hpp"
#include "runtime/objectMonitor.inline.hpp"
#include "runtime/thread.inline.hpp"
#include "runtime/threads.hpp"
#include "runtime/threadSMR.inline.hpp"
#include "runtime/vframe.hpp"
@ -70,6 +71,8 @@ PerfVariable* ThreadService::_daemon_threads_count = nullptr;
volatile int ThreadService::_atomic_threads_count = 0;
volatile int ThreadService::_atomic_daemon_threads_count = 0;
volatile jlong ThreadService::_exited_allocated_bytes = 0;
ThreadDumpResult* ThreadService::_threaddump_list = nullptr;
static const int INITIAL_ARRAY_SIZE = 10;
@ -157,6 +160,9 @@ void ThreadService::decrement_thread_counts(JavaThread* jt, bool daemon) {
void ThreadService::remove_thread(JavaThread* thread, bool daemon) {
assert(Threads_lock->owned_by_self(), "must have threads lock");
// Include hidden thread allcations in exited_allocated_bytes
ThreadService::incr_exited_allocated_bytes(thread->cooked_allocated_bytes());
// Do not count hidden threads
if (is_hidden_thread(thread)) {
return;

@ -61,6 +61,10 @@ private:
static PerfVariable* _peak_threads_count;
static PerfVariable* _daemon_threads_count;
// As could this...
// Number of heap bytes allocated by terminated threads.
static volatile jlong _exited_allocated_bytes;
// These 2 counters are like the above thread counts, but are
// atomically decremented in ThreadService::current_thread_exiting instead of
// ThreadService::remove_thread, so that the thread count is updated before
@ -102,6 +106,14 @@ public:
static jlong get_live_thread_count() { return _atomic_threads_count; }
static jlong get_daemon_thread_count() { return _atomic_daemon_threads_count; }
static jlong exited_allocated_bytes() { return Atomic::load(&_exited_allocated_bytes); }
static void incr_exited_allocated_bytes(jlong size) {
// No need for an atomic add because called under the Threads_lock,
// but because _exited_allocated_bytes is read concurrently, need
// atomic store to avoid readers seeing a partial update.
Atomic::store(&_exited_allocated_bytes, _exited_allocated_bytes + size);
}
// Support for thread dump
static void add_thread_dump(ThreadDumpResult* dump);
static void remove_thread_dump(ThreadDumpResult* dump);

@ -342,6 +342,13 @@ public class ThreadImpl implements ThreadMXBean {
}
}
protected long getTotalThreadAllocatedBytes() {
if (isThreadAllocatedMemoryEnabled()) {
return getTotalThreadAllocatedMemory();
}
return -1;
}
protected long getCurrentThreadAllocatedBytes() {
if (isThreadAllocatedMemoryEnabled() && !Thread.currentThread().isVirtual()) {
return getThreadAllocatedMemory0(0);
@ -525,6 +532,7 @@ public class ThreadImpl implements ThreadMXBean {
private static native void getThreadUserCpuTime1(long[] ids, long[] result);
private static native long getThreadAllocatedMemory0(long id);
private static native void getThreadAllocatedMemory1(long[] ids, long[] result);
private static native long getTotalThreadAllocatedMemory();
private static native void setThreadCpuTimeEnabled0(boolean enable);
private static native void setThreadAllocatedMemoryEnabled0(boolean enable);
private static native void setThreadContentionMonitoringEnabled0(boolean enable);

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2023, 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
@ -98,7 +98,7 @@ JNIEXPORT jlong JNICALL
Java_sun_management_ThreadImpl_getThreadAllocatedMemory0
(JNIEnv *env, jclass cls, jlong tid)
{
return jmm_interface->GetOneThreadAllocatedMemory(env, tid);
return jmm_interface->GetOneThreadAllocatedMemory(env, tid);
}
JNIEXPORT void JNICALL
@ -108,6 +108,13 @@ Java_sun_management_ThreadImpl_getThreadAllocatedMemory1
jmm_interface->GetThreadAllocatedMemory(env, ids, sizeArray);
}
JNIEXPORT jlong JNICALL
Java_sun_management_ThreadImpl_getTotalThreadAllocatedMemory
(JNIEnv *env, jclass cls)
{
return jmm_interface->GetTotalThreadAllocatedMemory(env);
}
JNIEXPORT jobjectArray JNICALL
Java_sun_management_ThreadImpl_findMonitorDeadlockedThreads0
(JNIEnv *env, jclass cls)

@ -105,6 +105,42 @@ public interface ThreadMXBean extends java.lang.management.ThreadMXBean {
*/
public long[] getThreadUserTime(long[] ids);
/**
* Returns an approximation of the total amount of memory, in bytes, allocated
* in heap memory by all threads since the Java virtual machine started.
* The returned value is an approximation because some Java virtual machine
* implementations may use object allocation mechanisms that result in a
* delay between the time an object is allocated and the time its size is
* recorded.
*
* @implSpec The default implementation throws {@code UnsupportedOperationException}
* if the Java virtual machine implementation does not support thread
* memory allocation measurement, and otherwise acts as though thread
* memory allocation measurement is disabled.
*
* @return an approximation of the total memory allocated, in bytes, in
* heap memory since the Java virtual machine was started,
* if thread memory allocation measurement is enabled;
* {@code -1} otherwise.
*
* @throws UnsupportedOperationException if the Java virtual
* machine implementation does not support thread memory allocation
* measurement.
*
* @see #isThreadAllocatedMemorySupported
* @see #isThreadAllocatedMemoryEnabled
* @see #setThreadAllocatedMemoryEnabled
*
* @since 21
*/
public default long getTotalThreadAllocatedBytes() {
if (!isThreadAllocatedMemorySupported()) {
throw new UnsupportedOperationException(
"Thread allocated memory measurement is not supported.");
}
return -1;
}
/**
* Returns an approximation of the total amount of memory, in bytes,
* allocated in heap memory for the current thread.

@ -57,6 +57,11 @@ public class HotSpotThreadImpl extends ThreadImpl implements ThreadMXBean {
return super.getThreadUserTime(ids);
}
@Override
public long getTotalThreadAllocatedBytes() {
return super.getTotalThreadAllocatedBytes();
}
@Override
public long getCurrentThreadAllocatedBytes() {
return super.getCurrentThreadAllocatedBytes();

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2023, 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
@ -23,7 +23,7 @@
/*
* @test
* @bug 6173675 8231209
* @bug 6173675 8231209 8304074
* @summary Basic test of ThreadMXBean.getThreadAllocatedBytes
* @author Paul Hohensee
*/
@ -33,6 +33,7 @@ import java.lang.management.*;
public class ThreadAllocatedMemory {
private static com.sun.management.ThreadMXBean mbean =
(com.sun.management.ThreadMXBean)ManagementFactory.getThreadMXBean();
private static boolean testFailed = false;
private static volatile boolean done = false;
private static volatile boolean done1 = false;
private static Object obj = new Object();
@ -55,6 +56,13 @@ public class ThreadAllocatedMemory {
// Test many threads that are not this one
testGetThreadsAllocatedBytes();
// Test cumulative Java thread allocation since JVM launch
testGetTotalThreadAllocatedBytes();
if (testFailed) {
throw new RuntimeException("TEST FAILED");
}
System.out.println("Test passed");
}
@ -92,13 +100,15 @@ public class ThreadAllocatedMemory {
}
private static void testGetCurrentThreadAllocatedBytes() {
Thread curThread = Thread.currentThread();
long size = mbean.getCurrentThreadAllocatedBytes();
ensureValidSize(size);
ensureValidSize(curThread, size);
// do some more allocation
doit();
checkResult(Thread.currentThread(), size,
checkResult(curThread, size,
mbean.getCurrentThreadAllocatedBytes());
}
@ -107,7 +117,7 @@ public class ThreadAllocatedMemory {
long id = curThread.getId();
long size = mbean.getThreadAllocatedBytes(id);
ensureValidSize(size);
ensureValidSize(curThread, size);
// do some more allocation
doit();
@ -119,7 +129,8 @@ public class ThreadAllocatedMemory {
throws Exception {
// start a thread
done = false; done1 = false;
done = false;
done1 = false;
Thread curThread = new MyThread("MyThread");
curThread.start();
long id = curThread.getId();
@ -128,7 +139,7 @@ public class ThreadAllocatedMemory {
waitUntilThreadBlocked(curThread);
long size = mbean.getThreadAllocatedBytes(id);
ensureValidSize(size);
ensureValidSize(curThread, size);
// let thread go to do some more allocation
synchronized (obj) {
@ -152,8 +163,7 @@ public class ThreadAllocatedMemory {
try {
curThread.join();
} catch (InterruptedException e) {
System.out.println("Unexpected exception is thrown.");
e.printStackTrace(System.out);
reportUnexpected(e, "during join");
}
}
@ -161,7 +171,8 @@ public class ThreadAllocatedMemory {
throws Exception {
// start threads
done = false; done1 = false;
done = false;
done1 = false;
for (int i = 0; i < NUM_THREADS; i++) {
threads[i] = new MyThread("MyThread-" + i);
threads[i].start();
@ -172,7 +183,7 @@ public class ThreadAllocatedMemory {
for (int i = 0; i < NUM_THREADS; i++) {
sizes[i] = mbean.getThreadAllocatedBytes(threads[i].getId());
ensureValidSize(sizes[i]);
ensureValidSize(threads[i], sizes[i]);
}
// let threads go to do some more allocation
@ -201,38 +212,106 @@ public class ThreadAllocatedMemory {
try {
threads[i].join();
} catch (InterruptedException e) {
System.out.println("Unexpected exception is thrown.");
e.printStackTrace(System.out);
reportUnexpected(e, "during join");
break;
}
}
}
private static void ensureValidSize(long size) {
private static void testGetTotalThreadAllocatedBytes()
throws Exception {
// baseline should be positive
Thread curThread = Thread.currentThread();
long cumulativeSize = mbean.getTotalThreadAllocatedBytes();
if (cumulativeSize <= 0) {
throw new RuntimeException(
"Invalid allocated bytes returned for " + curThread.getName() + " = " + cumulativeSize);
}
// start threads
done = false;
done1 = false;
for (int i = 0; i < NUM_THREADS; i++) {
threads[i] = new MyThread("MyThread-" + i);
threads[i].start();
}
// wait for threads to block after doing some allocation
waitUntilThreadsBlocked();
// check after threads are blocked
cumulativeSize = checkResult(curThread, cumulativeSize, mbean.getTotalThreadAllocatedBytes());
// let threads go to do some more allocation
synchronized (obj) {
done = true;
obj.notifyAll();
}
// wait for threads to get going again. we don't care if we
// catch them in mid-execution or if some of them haven't
// restarted after we're done sleeping.
goSleep(400);
System.out.println("Done sleeping");
// check while threads are running
cumulativeSize = checkResult(curThread, cumulativeSize, mbean.getTotalThreadAllocatedBytes());
// let threads exit
synchronized (obj) {
done1 = true;
obj.notifyAll();
}
for (int i = 0; i < NUM_THREADS; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
reportUnexpected(e, "during join");
break;
}
}
// check after threads exit
checkResult(curThread, cumulativeSize, mbean.getTotalThreadAllocatedBytes());
}
private static void ensureValidSize(Thread curThread, long size) {
// implementation could have started measurement when
// measurement was enabled, in which case size can be 0
if (size < 0) {
throw new RuntimeException(
"Invalid allocated bytes returned = " + size);
"Invalid allocated bytes returned for thread " +
curThread.getName() + " = " + size);
}
}
private static void checkResult(Thread curThread,
long prev_size, long curr_size) {
if (curr_size < prev_size) {
throw new RuntimeException("Allocated bytes " + curr_size +
" expected >= " + prev_size);
}
private static long checkResult(Thread curThread,
long prevSize, long currSize) {
System.out.println(curThread.getName() +
" Previous allocated bytes = " + prev_size +
" Current allocated bytes = " + curr_size);
" Previous allocated bytes = " + prevSize +
" Current allocated bytes = " + currSize);
if (currSize < prevSize) {
throw new RuntimeException("TEST FAILED: " +
curThread.getName() +
" previous allocated bytes = " + prevSize +
" > current allocated bytes = " + currSize);
}
return currSize;
}
private static void reportUnexpected(Exception e, String when) {
System.out.println("Unexpected exception thrown " + when + ".");
e.printStackTrace(System.out);
testFailed = true;
}
private static void goSleep(long ms) throws Exception {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
System.out.println("Unexpected exception is thrown.");
throw e;
}
}
@ -287,34 +366,23 @@ public class ThreadAllocatedMemory {
try {
obj.wait();
} catch (InterruptedException e) {
System.out.println("Unexpected exception is thrown.");
e.printStackTrace(System.out);
reportUnexpected(e, "while !done");
break;
}
}
}
long size1 = mbean.getThreadAllocatedBytes(getId());
long prevSize = mbean.getThreadAllocatedBytes(getId());
ThreadAllocatedMemory.doit();
long size2 = mbean.getThreadAllocatedBytes(getId());
System.out.println(getName() + ": " +
"ThreadAllocatedBytes = " + size1 +
" ThreadAllocatedBytes = " + size2);
if (size1 > size2) {
throw new RuntimeException(getName() +
" ThreadAllocatedBytes = " + size1 +
" > ThreadAllocatedBytes = " + size2);
}
long currSize = mbean.getThreadAllocatedBytes(getId());
checkResult(this, prevSize, currSize);
synchronized (obj) {
while (!done1) {
try {
obj.wait();
} catch (InterruptedException e) {
System.out.println("Unexpected exception is thrown.");
e.printStackTrace(System.out);
reportUnexpected(e, "while !done1");
break;
}
}

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2011, 2023, 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
@ -47,7 +47,6 @@ public class ThreadAllocatedMemoryArray {
return;
}
// start threads, wait for them to block
long[] ids = new long[NUM_THREADS];
@ -59,7 +58,6 @@ public class ThreadAllocatedMemoryArray {
waitUntilThreadBlocked();
// disable allocated memory measurement
if (mbean.isThreadAllocatedMemoryEnabled()) {
mbean.setThreadAllocatedMemoryEnabled(false);
@ -117,19 +115,9 @@ public class ThreadAllocatedMemoryArray {
// restarted after we're done sleeping.
goSleep(400);
long[] sizes1 = mbean.getThreadAllocatedBytes(ids);
long[] afterSizes = mbean.getThreadAllocatedBytes(ids);
for (int i = 0; i < NUM_THREADS; i++) {
long newSize = sizes1[i];
if (sizes[i] > newSize) {
throw new RuntimeException("TEST FAILED: " +
threads[i].getName() +
" previous allocated bytes = " + sizes[i] +
" > current allocated bytes = " + newSize);
}
System.out.println(threads[i].getName() +
" Previous allocated bytes = " + sizes[i] +
" Current allocated bytes = " + newSize);
checkResult(threads[i], sizes[i], afterSizes[i]);
}
try {
@ -147,7 +135,6 @@ public class ThreadAllocatedMemoryArray {
"Caught expected IllegalArgumentException: " + e.getMessage());
}
// let threads exit
synchronized (obj) {
done1 = true;
@ -158,9 +145,7 @@ public class ThreadAllocatedMemoryArray {
try {
threads[i].join();
} catch (InterruptedException e) {
System.out.println("Unexpected exception is thrown.");
e.printStackTrace(System.out);
testFailed = true;
reportUnexpected(e, "during join");
break;
}
}
@ -173,11 +158,30 @@ public class ThreadAllocatedMemoryArray {
}
private static void checkResult(Thread curThread,
long prevSize, long currSize) {
System.out.println(curThread.getName() +
" Previous allocated bytes = " + prevSize +
" Current allocated bytes = " + currSize);
if (currSize < prevSize) {
throw new RuntimeException("TEST FAILED: " +
curThread.getName() +
" previous allocated bytes = " + prevSize +
" > current allocated bytes = " + currSize);
}
}
private static void reportUnexpected(Exception e, String when) {
System.out.println("Unexpected exception thrown " + when + ".");
e.printStackTrace(System.out);
testFailed = true;
}
private static void goSleep(long ms) throws Exception {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
System.out.println("Unexpected exception is thrown.");
throw e;
}
}
@ -221,9 +225,7 @@ public class ThreadAllocatedMemoryArray {
try {
obj.wait();
} catch (InterruptedException e) {
System.out.println("Unexpected exception is thrown.");
e.printStackTrace(System.out);
testFailed = true;
reportUnexpected(e, "while !done");
break;
}
}
@ -236,9 +238,7 @@ public class ThreadAllocatedMemoryArray {
try {
obj.wait();
} catch (InterruptedException e) {
System.out.println("Unexpected exception is thrown.");
e.printStackTrace(System.out);
testFailed = true;
reportUnexpected(e, "while !done");
break;
}
}