Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2026, 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 @@ -289,6 +289,14 @@ public ThreadInfo[] dumpAllThreads(boolean lockedMonitors, boolean lockedSynchro
return new ThreadInfo[0];
}

@Override
public long getTotalThreadAllocatedBytes() {
if (!isThreadAllocatedMemoryEnabled()) {
return -1;
}
return PlatformThreads.getTotalThreadAllocatedBytes();
}

@Override
public long getThreadAllocatedBytes(long id) {
boolean valid = verifyThreadAllocatedMemory(id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@ public static PlatformThreads singleton() {
*/
private final UninterruptibleUtils.AtomicInteger unattachedStartedThreads = new UninterruptibleUtils.AtomicInteger(0);

/** Maximum reported total of allocated bytes, retained so reported totals never decrease. */
private static final UninterruptibleUtils.AtomicLong maxReportedTotalAllocatedBytes = new UninterruptibleUtils.AtomicLong(0);

/** Bytes allocated by detached threads. Protected by {@link ThreadsLock}. */
private static long allocatedBytesOfDetachedThreads;

/** The default group for new Threads that are attached without an explicit group. */
final ThreadGroup mainGroup;
/** The root group for all threads. */
Expand Down Expand Up @@ -241,6 +247,55 @@ public static long getThreadCpuTime(long javaThreadId, boolean includeSystemTime
}
}

/**
* Returns an approximation of the heap bytes allocated by live and detached threads.
* <p>
* TLAB allocations can temporarily be counted twice if a reader sees the updated cumulative
* count before the retired TLAB descriptor is cleared. A later sample can therefore be smaller.
* To keep results from decreasing, this method retains the maximum reported total even after
* the allocating thread exits. This can preserve an overestimate until further allocations make
* up the difference.
* <p>
* If the computed total wraps to a negative value, the maximum reported total is returned.
* The counters continue wrapping. A later positive total can replace the maximum reported total
* if it is larger. The counters do not saturate at {@link Long#MAX_VALUE}.
*/
@BasedOnJDKFile("https://github.com/graalvm/labs-openjdk/blob/jdk-25.0.5.1+0/src/hotspot/share/services/management.cpp#L2093-L2126")
public static long getTotalThreadAllocatedBytes() {
long totalAllocatedBytes = computeTotalThreadAllocatedBytes();

/* Unlike HotSpot's MonitoringSupport_lock, an atomic update protects the maximum reported total. */
long previousMaxReportedTotalAllocatedBytes;
do {
previousMaxReportedTotalAllocatedBytes = maxReportedTotalAllocatedBytes.get();
if (totalAllocatedBytes <= previousMaxReportedTotalAllocatedBytes) {
return previousMaxReportedTotalAllocatedBytes;
}
} while (!maxReportedTotalAllocatedBytes.compareAndSet(previousMaxReportedTotalAllocatedBytes, totalAllocatedBytes));

return totalAllocatedBytes;
}

private static long computeTotalThreadAllocatedBytes() {
/*
* Unlike HotSpot's thread-list handle, our threads lock protects both list removal and
* allocation accounting in VMThreads.detachThread. A reader cannot miss a thread between its
* removal from the list and the update to the detached-thread allocation total.
*/
ThreadsLock.lockRead();
try {
long totalAllocatedBytes = allocatedBytesOfDetachedThreads;
for (IsolateThread thread = VMThreads.firstThread(); thread.isNonNull(); thread = VMThreads.nextThread(thread)) {
long allocatedBytes = Heap.getHeap().getThreadAllocatedMemory(thread);
totalAllocatedBytes += allocatedBytes;
}

return totalAllocatedBytes;
} finally {
ThreadsLock.unlockRead();
}
}

public static void getThreadAllocatedBytes(long[] javaThreadIds, long[] result) {
ThreadsLock.lockRead();
try {
Expand All @@ -251,7 +306,6 @@ public static void getThreadAllocatedBytes(long[] javaThreadIds, long[] result)
for (int i = 0; i < javaThreadIds.length; i++) {
if (JavaThreads.getThreadId(javaThread) == javaThreadIds[i]) {
result[i] = Heap.getHeap().getThreadAllocatedMemory(isolateThread);
break;
}
}
}
Expand Down Expand Up @@ -588,6 +642,12 @@ public long getMainThreadRunnerJavaStackSize() {
}

@Uninterruptible(reason = "Thread is detaching and holds the ThreadsLock with exclusive write access.")
static void recordAllocatedBytesOfDetachedThread(IsolateThread vmThread) {
assert ThreadsLock.hasExclusiveWriteAccess();
allocatedBytesOfDetachedThreads += Heap.getHeap().getThreadAllocatedMemory(vmThread);
}

@Uninterruptible(reason = "Thread is detaching or the isolate is being torn down.")
public static void detach(IsolateThread vmThread) {
Thread thread = currentThread.get(vmThread);
if (thread != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,9 @@ protected void detachThread(IsolateThread thread) {
try {
removeFromThreadList(thread);
PlatformThreads.detach(thread);

/* Record the thread's allocated bytes before heap detachment releases its allocation buffers. */
PlatformThreads.recordAllocatedBytesOfDetachedThread(thread);
Heap.getHeap().detachThread(thread);

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,10 @@ protected Heap() {
public abstract void attachThread(IsolateThread isolateThread);

/**
* Notifies the heap that a thread will be detached from the VM. This allows to cleanup
* heap-specific resources, e.g., the TLAB. This method is called for every thread except the
* main thread (i.e., the one that maps the image heap).
* Notifies the heap that a thread will be detached from the VM. This allows cleaning up
* heap-specific resources, e.g., the TLAB. During isolate teardown, this method is not called
* for the last attached thread. Its remaining heap-specific resources are released by
* {@link #tearDown}.
*/
@Uninterruptible(reason = "Current thread holds the ThreadsLock with exclusive write access.")
public abstract void detachThread(IsolateThread isolateThread);
Expand Down Expand Up @@ -128,7 +129,13 @@ public void visitLoadedClasses(Consumer<Class<?>> visitor) {
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public abstract ObjectHeader getObjectHeader();

/** Tear down the heap and free all allocated virtual memory chunks. */
/**
* Tears down the heap and frees all allocated virtual memory chunks.
* <p>
* Once this method is called, only the thread tearing down the isolate is still attached.
* The implementation must also release the heap-specific resources of this last thread, as
* {@link #detachThread} does for other threads.
*/
@Uninterruptible(reason = "Tear-down in progress.")
public abstract boolean tearDown();

Expand Down