1 /* 2 * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.internal.vm; 26 27 import java.util.Arrays; 28 import java.util.stream.Stream; 29 30 /** 31 * Represents a snapshot of information about a Thread. 32 */ 33 class ThreadSnapshot { 34 private static final StackTraceElement[] EMPTY_STACK = new StackTraceElement[0]; 35 private static final ThreadLock[] EMPTY_LOCKS = new ThreadLock[0]; 36 37 private String name; 38 private int threadStatus; 39 private StackTraceElement[] stackTrace; 40 private ThreadLock[] locks; 41 42 /** 43 * Take a snapshot of a Thread to get all information about the thread. 44 */ 45 static ThreadSnapshot of(Thread thread) { 46 ThreadSnapshot snapshot = create(thread, true); 47 if (snapshot.stackTrace == null) { 48 snapshot.stackTrace = EMPTY_STACK; 49 } 50 snapshot.locks = snapshot.locks == null 51 ? snapshot.locks = EMPTY_LOCKS 52 : ThreadLock.of(snapshot.locks); 53 return snapshot; 54 } 55 56 /** 57 * Returns the thread name. 58 */ 59 String threadName() { 60 return name; 61 } 62 63 /** 64 * Returns the thread state. 65 */ 66 Thread.State threadState() { 67 // is this valid for virtual threads 68 return jdk.internal.misc.VM.toThreadState(threadStatus); 69 } 70 71 /** 72 * Returns the thread stack trace. 73 */ 74 StackTraceElement[] stackTrace() { 75 return stackTrace; 76 } 77 78 /** 79 * Returns the thread's parkBlocker. 80 */ 81 Object parkBlocker() { 82 return findLockObject(0, LockType.PARKING_TO_WAIT) 83 .findAny() 84 .orElse(null); 85 } 86 87 /** 88 * Returns the owner of exclusive mode synchronizer when the parkBlocker is an AQS. 89 */ 90 Object exclusiveOwnerThread() { 91 return findLockObject(0, LockType.OWNABLE_SYNCHRONIZER) 92 .findAny() 93 .orElse(null); 94 } 95 96 /** 97 * Returns the object that the thread is blocked on. 98 * @throws IllegalStateException if not in the blocked state 99 */ 100 Object blockedOn() { 101 if (threadState() != Thread.State.BLOCKED) { 102 throw new IllegalStateException(); 103 } 104 return findLockObject(0, LockType.WAITING_TO_LOCK) 105 .findAny() 106 .orElse(null); 107 } 108 109 /** 110 * Returns the object that the thread is waiting on. 111 * @throws IllegalStateException if not in the waiting state 112 */ 113 Object waitingOn() { 114 if (threadState() != Thread.State.WAITING 115 && threadState() != Thread.State.TIMED_WAITING) { 116 throw new IllegalStateException(); 117 } 118 return findLockObject(0, LockType.WAITING_ON) 119 .findAny() 120 .orElse(null); 121 } 122 123 /** 124 * Returns true if the thread owns any object monitors. 125 */ 126 boolean ownsMonitors() { 127 return Arrays.stream(locks) 128 .anyMatch(lock -> lock.type() == LockType.LOCKED); 129 } 130 131 /** 132 * Returns the objects that the thread locked at the given depth. 133 */ 134 Stream<Object> ownedMonitorsAt(int depth) { 135 return findLockObject(depth, LockType.LOCKED); 136 } 137 138 private Stream<Object> findLockObject(int depth, LockType type) { 139 return Arrays.stream(locks) 140 .filter(lock -> lock.depth() == depth 141 && lock.type() == type 142 && lock.lockObject() != null) 143 .map(ThreadLock::lockObject); 144 } 145 146 /** 147 * If the thread is a mounted virtual thread then return its carrier. 148 */ 149 Thread carrierThread() { 150 return null; 151 } 152 153 /** 154 * Represents information about a locking operation. 155 */ 156 private enum LockType { 157 // Park blocker 158 PARKING_TO_WAIT, 159 // Lock object is a class of the eliminated monitor 160 ELIMINATED_SCALAR_REPLACED, 161 ELIMINATED_MONITOR, 162 LOCKED, 163 WAITING_TO_LOCK, 164 WAITING_ON, 165 WAITING_TO_RELOCK, 166 // No corresponding stack frame, depth is always == -1 167 OWNABLE_SYNCHRONIZER 168 } 169 170 /** 171 * Represents a locking operation of a thread at a specific stack depth. 172 */ 173 private class ThreadLock { 174 private static final LockType[] lockTypeValues = LockType.values(); // cache 175 176 // set by the VM 177 private int depth; 178 private int typeOrdinal; 179 private Object obj; 180 181 private LockType type; 182 183 static ThreadLock[] of(ThreadLock[] locks) { 184 for (ThreadLock lock: locks) { 185 lock.type = lockTypeValues[lock.typeOrdinal]; 186 } 187 return locks; 188 } 189 190 int depth() { 191 return depth; 192 } 193 194 LockType type() { 195 return type; 196 } 197 198 Object lockObject() { 199 if (type == LockType.ELIMINATED_SCALAR_REPLACED) { 200 // we have no lock object, lock contains lock class 201 return null; 202 } 203 return obj; 204 } 205 } 206 207 private static native ThreadSnapshot create(Thread thread, boolean withLocks); 208 }