Skip to content

Commit

Permalink
8304074: [JMX] Add an approximation of total bytes allocated on the J…
Browse files Browse the repository at this point in the history
…ava heap by the JVM

Reviewed-by: simonis
Backport-of: 3eced01f9efe2567a07b63343f8559683a2d0517
  • Loading branch information
Paul Hohensee committed Dec 7, 2023
1 parent 9bf8c2e commit 0bbbd14
Show file tree
Hide file tree
Showing 11 changed files with 250 additions and 80 deletions.
5 changes: 3 additions & 2 deletions src/hotspot/share/include/jmm.h
@@ -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
Expand Down Expand Up @@ -316,7 +316,8 @@ typedef struct jmmInterface_1_ {
void (JNICALL *SetVMGlobal) (JNIEnv *env,
jstring flag_name,
jvalue new_value);
void* reserved6;
jlong (JNICALL *GetTotalThreadAllocatedMemory)
(JNIEnv *env);
jobjectArray (JNICALL *DumpThreads) (JNIEnv *env,
jlongArray ids,
jboolean lockedMonitors,
Expand Down
2 changes: 1 addition & 1 deletion src/hotspot/share/runtime/mutexLocker.hpp
Expand Up @@ -74,7 +74,7 @@ extern Mutex* Shared_DirtyCardQ_lock; // Lock protecting dirty card
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 Mutex* ParGCRareEvent_lock; // Synchronizes various (rare) parallel GC ops.
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)
Expand Down
46 changes: 37 additions & 9 deletions src/hotspot/share/services/management.cpp
Expand Up @@ -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"
Expand Down Expand Up @@ -413,8 +414,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();

Expand All @@ -430,8 +429,6 @@ static void validate_thread_id_array(typeArrayHandle ids_ah, TRAPS) {
}
}

#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);
Expand Down Expand Up @@ -2065,7 +2062,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.
Expand Down Expand Up @@ -2199,9 +2230,6 @@ JVM_ENTRY(void, jmm_GetThreadCpuTimesWithKind(JNIEnv *env, jlongArray ids,
}
JVM_END



#if INCLUDE_MANAGEMENT
const struct jmmInterface_1_ jmm_interface = {
NULL,
NULL,
Expand Down Expand Up @@ -2235,7 +2263,7 @@ const struct jmmInterface_1_ jmm_interface = {
jmm_DumpHeap0,
jmm_FindDeadlockedThreads,
jmm_SetVMGlobal,
NULL,
jmm_GetTotalThreadAllocatedMemory,
jmm_DumpThreads,
jmm_SetGCNotificationEnabled,
jmm_GetDiagnosticCommands,
Expand Down
5 changes: 5 additions & 0 deletions src/hotspot/share/services/threadService.cpp
Expand Up @@ -68,6 +68,8 @@ PerfVariable* ThreadService::_daemon_threads_count = NULL;
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 = NULL;

static const int INITIAL_ARRAY_SIZE = 10;
Expand Down Expand Up @@ -155,6 +157,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;
Expand Down
12 changes: 12 additions & 0 deletions src/hotspot/share/services/threadService.hpp
Expand Up @@ -60,6 +60,10 @@ class ThreadService : public AllStatic {
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
Expand Down Expand Up @@ -98,6 +102,14 @@ class ThreadService : public AllStatic {
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);
Expand Down
Expand Up @@ -349,6 +349,13 @@ public void setThreadCpuTimeEnabled(boolean enable) {
}
}

protected long getTotalThreadAllocatedBytes() {
if (isThreadAllocatedMemoryEnabled()) {
return getTotalThreadAllocatedMemory();
}
return -1;
}

protected long getCurrentThreadAllocatedBytes() {
if (isThreadAllocatedMemoryEnabled()) {
return getThreadAllocatedMemory0(0);
Expand Down Expand Up @@ -532,6 +539,7 @@ private static native void getThreadInfo1(long[] ids,
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);
Expand Down
11 changes: 9 additions & 2 deletions src/java.management/share/native/libmanagement/ThreadImpl.c
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
Expand Up @@ -107,6 +107,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 17.0.11
*/
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.
Expand Down
Expand Up @@ -57,6 +57,11 @@ public long[] getThreadUserTime(long[] ids) {
return super.getThreadUserTime(ids);
}

@Override
public long getTotalThreadAllocatedBytes() {
return super.getTotalThreadAllocatedBytes();
}

@Override
public long getCurrentThreadAllocatedBytes() {
return super.getCurrentThreadAllocatedBytes();
Expand Down

1 comment on commit 0bbbd14

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

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

Please sign in to comment.