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: dholmes, mchung
  • Loading branch information
Paul Hohensee committed May 30, 2023
1 parent 15e0285 commit 3eced01
Show file tree
Hide file tree
Showing 11 changed files with 253 additions and 79 deletions.
7 changes: 5 additions & 2 deletions src/hotspot/share/include/jmm.h
Original file line number Diff line number Diff line change
@@ -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 @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/hotspot/share/runtime/mutexLocker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 37 additions & 8 deletions src/hotspot/share/services/management.cpp
Original file line number Diff line number Diff line change
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 @@ -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();

Expand All @@ -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);
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -2241,6 +2269,7 @@ const struct jmmInterface_1_ jmm_interface = {
jmm_GetMemoryManagers,
jmm_GetMemoryPoolUsage,
jmm_GetPeakMemoryPoolUsage,
jmm_GetTotalThreadAllocatedMemory,
jmm_GetOneThreadAllocatedMemory,
jmm_GetThreadAllocatedMemory,
jmm_GetMemoryUsage,
Expand Down
6 changes: 6 additions & 0 deletions src/hotspot/share/services/threadService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions src/hotspot/share/services/threadService.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,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 @@ -102,6 +106,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
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,13 @@ public void setThreadCpuTimeEnabled(boolean enable) {
}
}

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

protected long getCurrentThreadAllocatedBytes() {
if (isThreadAllocatedMemoryEnabled() && !Thread.currentThread().isVirtual()) {
return getThreadAllocatedMemory0(0);
Expand Down Expand Up @@ -525,6 +532,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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
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
Loading

1 comment on commit 3eced01

@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.