Skip to content

Commit 0bbbd14

Browse files
author
Paul Hohensee
committed
8304074: [JMX] Add an approximation of total bytes allocated on the Java heap by the JVM
Reviewed-by: simonis Backport-of: 3eced01f9efe2567a07b63343f8559683a2d0517
1 parent 9bf8c2e commit 0bbbd14

File tree

11 files changed

+250
-80
lines changed

11 files changed

+250
-80
lines changed

src/hotspot/share/include/jmm.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -316,7 +316,8 @@ typedef struct jmmInterface_1_ {
316316
void (JNICALL *SetVMGlobal) (JNIEnv *env,
317317
jstring flag_name,
318318
jvalue new_value);
319-
void* reserved6;
319+
jlong (JNICALL *GetTotalThreadAllocatedMemory)
320+
(JNIEnv *env);
320321
jobjectArray (JNICALL *DumpThreads) (JNIEnv *env,
321322
jlongArray ids,
322323
jboolean lockedMonitors,

src/hotspot/share/runtime/mutexLocker.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ extern Mutex* Shared_DirtyCardQ_lock; // Lock protecting dirty card
7474
extern Mutex* G1DetachedRefinementStats_lock; // Lock protecting detached refinement stats
7575
extern Mutex* MarkStackFreeList_lock; // Protects access to the global mark stack free list.
7676
extern Mutex* MarkStackChunkList_lock; // Protects access to the global mark stack chunk list.
77-
extern Mutex* MonitoringSupport_lock; // Protects updates to the serviceability memory pools.
77+
extern Mutex* MonitoringSupport_lock; // Protects updates to the serviceability memory pools and allocated memory high water mark.
7878
extern Mutex* ParGCRareEvent_lock; // Synchronizes various (rare) parallel GC ops.
7979
extern Monitor* ConcurrentGCBreakpoints_lock; // Protects concurrent GC breakpoint management
8080
extern Mutex* Compile_lock; // a lock held when Compilation is updating code (used to block CodeCache traversal, CHA updates, etc)

src/hotspot/share/services/management.cpp

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
#include "runtime/interfaceSupport.inline.hpp"
4848
#include "runtime/javaCalls.hpp"
4949
#include "runtime/jniHandles.inline.hpp"
50+
#include "runtime/mutexLocker.hpp"
5051
#include "runtime/notificationThread.hpp"
5152
#include "runtime/os.hpp"
5253
#include "runtime/thread.inline.hpp"
@@ -413,8 +414,6 @@ static MemoryPool* get_memory_pool_from_jobject(jobject obj, TRAPS) {
413414
return MemoryService::get_memory_pool(ph);
414415
}
415416

416-
#endif // INCLUDE_MANAGEMENT
417-
418417
static void validate_thread_id_array(typeArrayHandle ids_ah, TRAPS) {
419418
int num_threads = ids_ah->length();
420419

@@ -430,8 +429,6 @@ static void validate_thread_id_array(typeArrayHandle ids_ah, TRAPS) {
430429
}
431430
}
432431

433-
#if INCLUDE_MANAGEMENT
434-
435432
static void validate_thread_info_array(objArrayHandle infoArray_h, TRAPS) {
436433
// check if the element of infoArray is of type ThreadInfo class
437434
Klass* threadinfo_klass = Management::java_lang_management_ThreadInfo_klass(CHECK);
@@ -2065,7 +2062,41 @@ jlong Management::ticks_to_ms(jlong ticks) {
20652062
return (jlong)(((double)ticks / (double)os::elapsed_frequency())
20662063
* (double)1000.0);
20672064
}
2068-
#endif // INCLUDE_MANAGEMENT
2065+
2066+
// Gets the amount of memory allocated on the Java heap since JVM launch.
2067+
JVM_ENTRY(jlong, jmm_GetTotalThreadAllocatedMemory(JNIEnv *env))
2068+
// A thread increments exited_allocated_bytes in ThreadService::remove_thread
2069+
// only after it removes itself from the threads list, and once a TLH is
2070+
// created, no thread it references can remove itself from the threads
2071+
// list, so none can update exited_allocated_bytes. We therefore initialize
2072+
// result with exited_allocated_bytes after after we create the TLH so that
2073+
// the final result can only be short due to (1) threads that start after
2074+
// the TLH is created, or (2) terminating threads that escape TLH creation
2075+
// and don't update exited_allocated_bytes before we initialize result.
2076+
2077+
// We keep a high water mark to ensure monotonicity in case threads counted
2078+
// on a previous call end up in state (2).
2079+
static jlong high_water_result = 0;
2080+
2081+
JavaThreadIteratorWithHandle jtiwh;
2082+
jlong result = ThreadService::exited_allocated_bytes();
2083+
for (; JavaThread* thread = jtiwh.next();) {
2084+
jlong size = thread->cooked_allocated_bytes();
2085+
result += size;
2086+
}
2087+
2088+
{
2089+
MutexLocker ml(MonitoringSupport_lock, Mutex::_no_safepoint_check_flag);
2090+
if (result < high_water_result) {
2091+
// Encountered (2) above, or result wrapped to a negative value. In
2092+
// the latter case, it's pegged at the last positive value.
2093+
result = high_water_result;
2094+
} else {
2095+
high_water_result = result;
2096+
}
2097+
}
2098+
return result;
2099+
JVM_END
20692100

20702101
// Gets the amount of memory allocated on the Java heap for a single thread.
20712102
// Returns -1 if the thread does not exist or has terminated.
@@ -2199,9 +2230,6 @@ JVM_ENTRY(void, jmm_GetThreadCpuTimesWithKind(JNIEnv *env, jlongArray ids,
21992230
}
22002231
JVM_END
22012232

2202-
2203-
2204-
#if INCLUDE_MANAGEMENT
22052233
const struct jmmInterface_1_ jmm_interface = {
22062234
NULL,
22072235
NULL,
@@ -2235,7 +2263,7 @@ const struct jmmInterface_1_ jmm_interface = {
22352263
jmm_DumpHeap0,
22362264
jmm_FindDeadlockedThreads,
22372265
jmm_SetVMGlobal,
2238-
NULL,
2266+
jmm_GetTotalThreadAllocatedMemory,
22392267
jmm_DumpThreads,
22402268
jmm_SetGCNotificationEnabled,
22412269
jmm_GetDiagnosticCommands,

src/hotspot/share/services/threadService.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ PerfVariable* ThreadService::_daemon_threads_count = NULL;
6868
volatile int ThreadService::_atomic_threads_count = 0;
6969
volatile int ThreadService::_atomic_daemon_threads_count = 0;
7070

71+
volatile jlong ThreadService::_exited_allocated_bytes = 0;
72+
7173
ThreadDumpResult* ThreadService::_threaddump_list = NULL;
7274

7375
static const int INITIAL_ARRAY_SIZE = 10;
@@ -155,6 +157,9 @@ void ThreadService::decrement_thread_counts(JavaThread* jt, bool daemon) {
155157
void ThreadService::remove_thread(JavaThread* thread, bool daemon) {
156158
assert(Threads_lock->owned_by_self(), "must have threads lock");
157159

160+
// Include hidden thread allcations in exited_allocated_bytes
161+
ThreadService::incr_exited_allocated_bytes(thread->cooked_allocated_bytes());
162+
158163
// Do not count hidden threads
159164
if (is_hidden_thread(thread)) {
160165
return;

src/hotspot/share/services/threadService.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ class ThreadService : public AllStatic {
6060
static PerfVariable* _peak_threads_count;
6161
static PerfVariable* _daemon_threads_count;
6262

63+
// As could this...
64+
// Number of heap bytes allocated by terminated threads.
65+
static volatile jlong _exited_allocated_bytes;
66+
6367
// These 2 counters are like the above thread counts, but are
6468
// atomically decremented in ThreadService::current_thread_exiting instead of
6569
// ThreadService::remove_thread, so that the thread count is updated before
@@ -98,6 +102,14 @@ class ThreadService : public AllStatic {
98102
static jlong get_live_thread_count() { return _atomic_threads_count; }
99103
static jlong get_daemon_thread_count() { return _atomic_daemon_threads_count; }
100104

105+
static jlong exited_allocated_bytes() { return Atomic::load(&_exited_allocated_bytes); }
106+
static void incr_exited_allocated_bytes(jlong size) {
107+
// No need for an atomic add because called under the Threads_lock,
108+
// but because _exited_allocated_bytes is read concurrently, need
109+
// atomic store to avoid readers seeing a partial update.
110+
Atomic::store(&_exited_allocated_bytes, _exited_allocated_bytes + size);
111+
}
112+
101113
// Support for thread dump
102114
static void add_thread_dump(ThreadDumpResult* dump);
103115
static void remove_thread_dump(ThreadDumpResult* dump);

src/java.management/share/classes/sun/management/ThreadImpl.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,13 @@ public void setThreadCpuTimeEnabled(boolean enable) {
349349
}
350350
}
351351

352+
protected long getTotalThreadAllocatedBytes() {
353+
if (isThreadAllocatedMemoryEnabled()) {
354+
return getTotalThreadAllocatedMemory();
355+
}
356+
return -1;
357+
}
358+
352359
protected long getCurrentThreadAllocatedBytes() {
353360
if (isThreadAllocatedMemoryEnabled()) {
354361
return getThreadAllocatedMemory0(0);
@@ -532,6 +539,7 @@ private static native void getThreadInfo1(long[] ids,
532539
private static native void getThreadUserCpuTime1(long[] ids, long[] result);
533540
private static native long getThreadAllocatedMemory0(long id);
534541
private static native void getThreadAllocatedMemory1(long[] ids, long[] result);
542+
private static native long getTotalThreadAllocatedMemory();
535543
private static native void setThreadCpuTimeEnabled0(boolean enable);
536544
private static native void setThreadAllocatedMemoryEnabled0(boolean enable);
537545
private static native void setThreadContentionMonitoringEnabled0(boolean enable);

src/java.management/share/native/libmanagement/ThreadImpl.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -98,7 +98,7 @@ JNIEXPORT jlong JNICALL
9898
Java_sun_management_ThreadImpl_getThreadAllocatedMemory0
9999
(JNIEnv *env, jclass cls, jlong tid)
100100
{
101-
return jmm_interface->GetOneThreadAllocatedMemory(env, tid);
101+
return jmm_interface->GetOneThreadAllocatedMemory(env, tid);
102102
}
103103

104104
JNIEXPORT void JNICALL
@@ -108,6 +108,13 @@ Java_sun_management_ThreadImpl_getThreadAllocatedMemory1
108108
jmm_interface->GetThreadAllocatedMemory(env, ids, sizeArray);
109109
}
110110

111+
JNIEXPORT jlong JNICALL
112+
Java_sun_management_ThreadImpl_getTotalThreadAllocatedMemory
113+
(JNIEnv *env, jclass cls)
114+
{
115+
return jmm_interface->GetTotalThreadAllocatedMemory(env);
116+
}
117+
111118
JNIEXPORT jobjectArray JNICALL
112119
Java_sun_management_ThreadImpl_findMonitorDeadlockedThreads0
113120
(JNIEnv *env, jclass cls)

src/jdk.management/share/classes/com/sun/management/ThreadMXBean.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,42 @@ public interface ThreadMXBean extends java.lang.management.ThreadMXBean {
107107
*/
108108
public long[] getThreadUserTime(long[] ids);
109109

110+
/**
111+
* Returns an approximation of the total amount of memory, in bytes, allocated
112+
* in heap memory by all threads since the Java virtual machine started.
113+
* The returned value is an approximation because some Java virtual machine
114+
* implementations may use object allocation mechanisms that result in a
115+
* delay between the time an object is allocated and the time its size is
116+
* recorded.
117+
*
118+
* @implSpec The default implementation throws {@code UnsupportedOperationException}
119+
* if the Java virtual machine implementation does not support thread
120+
* memory allocation measurement, and otherwise acts as though thread
121+
* memory allocation measurement is disabled.
122+
*
123+
* @return an approximation of the total memory allocated, in bytes, in
124+
* heap memory since the Java virtual machine was started,
125+
* if thread memory allocation measurement is enabled;
126+
* {@code -1} otherwise.
127+
*
128+
* @throws UnsupportedOperationException if the Java virtual
129+
* machine implementation does not support thread memory allocation
130+
* measurement.
131+
*
132+
* @see #isThreadAllocatedMemorySupported
133+
* @see #isThreadAllocatedMemoryEnabled
134+
* @see #setThreadAllocatedMemoryEnabled
135+
*
136+
* @since 17.0.11
137+
*/
138+
public default long getTotalThreadAllocatedBytes() {
139+
if (!isThreadAllocatedMemorySupported()) {
140+
throw new UnsupportedOperationException(
141+
"Thread allocated memory measurement is not supported.");
142+
}
143+
return -1;
144+
}
145+
110146
/**
111147
* Returns an approximation of the total amount of memory, in bytes,
112148
* allocated in heap memory for the current thread.

src/jdk.management/share/classes/com/sun/management/internal/HotSpotThreadImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ public long[] getThreadUserTime(long[] ids) {
5757
return super.getThreadUserTime(ids);
5858
}
5959

60+
@Override
61+
public long getTotalThreadAllocatedBytes() {
62+
return super.getTotalThreadAllocatedBytes();
63+
}
64+
6065
@Override
6166
public long getCurrentThreadAllocatedBytes() {
6267
return super.getCurrentThreadAllocatedBytes();

0 commit comments

Comments
 (0)