Alex Menkov 406f1bc5b9 8357650: ThreadSnapshot to take snapshot of thread for thread dumps
Co-authored-by: Alan Bateman <alanb@openjdk.org>
Co-authored-by: Alex Menkov <amenkov@openjdk.org>
Reviewed-by: sspitsyn, kevinw
2025-06-03 18:46:55 +00:00

218 lines
6.4 KiB
Java

/*
* Copyright (c) 2025, 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.vm;
import java.util.Arrays;
import java.util.stream.Stream;
/**
* Represents a snapshot of information about a Thread.
*/
class ThreadSnapshot {
private static final StackTraceElement[] EMPTY_STACK = new StackTraceElement[0];
private static final ThreadLock[] EMPTY_LOCKS = new ThreadLock[0];
// filled by VM
private String name;
private int threadStatus;
private Thread carrierThread;
private StackTraceElement[] stackTrace;
// owned monitors
private ThreadLock[] locks;
// an object the thread is blocked/waiting on, converted to ThreadBlocker by ThreadSnapshot.of()
private int blockerTypeOrdinal;
private Object blockerObject;
// set by ThreadSnapshot.of()
private ThreadBlocker blocker;
private ThreadSnapshot() {}
/**
* Take a snapshot of a Thread to get all information about the thread.
*/
static ThreadSnapshot of(Thread thread) {
ThreadSnapshot snapshot = create(thread);
if (snapshot.stackTrace == null) {
snapshot.stackTrace = EMPTY_STACK;
}
if (snapshot.locks != null) {
Arrays.stream(snapshot.locks).forEach(ThreadLock::finishInit);
} else {
snapshot.locks = EMPTY_LOCKS;
}
if (snapshot.blockerObject != null) {
snapshot.blocker = new ThreadBlocker(snapshot.blockerTypeOrdinal, snapshot.blockerObject);
snapshot.blockerObject = null; // release
}
return snapshot;
}
/**
* Returns the thread name.
*/
String threadName() {
return name;
}
/**
* Returns the thread state.
*/
Thread.State threadState() {
return jdk.internal.misc.VM.toThreadState(threadStatus);
}
/**
* Returns the thread stack trace.
*/
StackTraceElement[] stackTrace() {
return stackTrace;
}
/**
* Returns the thread's parkBlocker.
*/
Object parkBlocker() {
return getBlocker(BlockerLockType.PARK_BLOCKER);
}
/**
* Returns the object that the thread is blocked on.
* @throws IllegalStateException if not in the blocked state
*/
Object blockedOn() {
if (threadState() != Thread.State.BLOCKED) {
throw new IllegalStateException();
}
return getBlocker(BlockerLockType.WAITING_TO_LOCK);
}
/**
* Returns the object that the thread is waiting on.
* @throws IllegalStateException if not in the waiting state
*/
Object waitingOn() {
if (threadState() != Thread.State.WAITING
&& threadState() != Thread.State.TIMED_WAITING) {
throw new IllegalStateException();
}
return getBlocker(BlockerLockType.WAITING_ON);
}
private Object getBlocker(BlockerLockType type) {
return (blocker != null && blocker.type == type) ? blocker.obj : null;
}
/**
* Returns true if the thread owns any object monitors.
*/
boolean ownsMonitors() {
return locks.length > 0;
}
/**
* Returns the objects that the thread locked at the given depth. The stream
* will contain a null element for a monitor that has been eliminated.
*/
Stream<Object> ownedMonitorsAt(int depth) {
return Arrays.stream(locks)
.filter(lock -> lock.depth() == depth)
.map(lock -> (lock.type == OwnedLockType.LOCKED)
? lock.lockObject()
: /*eliminated*/ null);
}
/**
* If the thread is a mounted virtual thread then return its carrier.
*/
Thread carrierThread() {
return carrierThread;
}
/**
* Represents information about a locking operation.
*/
private enum OwnedLockType {
LOCKED,
// Lock object is a class of the eliminated monitor
ELIMINATED,
}
private enum BlockerLockType {
// Park blocker
PARK_BLOCKER,
WAITING_TO_LOCK,
// Object.wait()
WAITING_ON,
}
/**
* Represents a locking operation of a thread at a specific stack depth.
*/
private class ThreadLock {
private static final OwnedLockType[] lockTypeValues = OwnedLockType.values(); // cache
// set by the VM
private int depth;
private int typeOrdinal;
private Object obj;
// set by ThreadLock.of()
private OwnedLockType type;
private ThreadLock() {}
void finishInit() {
type = lockTypeValues[typeOrdinal];
}
int depth() {
return depth;
}
OwnedLockType type() {
return type;
}
Object lockObject() {
if (type == OwnedLockType.ELIMINATED) {
// we have no lock object, lock contains lock class
return null;
}
return obj;
}
}
private record ThreadBlocker(BlockerLockType type, Object obj) {
private static final BlockerLockType[] lockTypeValues = BlockerLockType.values(); // cache
ThreadBlocker(int typeOrdinal, Object obj) {
this(lockTypeValues[typeOrdinal], obj);
}
}
private static native ThreadSnapshot create(Thread thread);
}