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 import jdk.internal.access.JavaLangAccess; 30 import jdk.internal.access.SharedSecrets; 31 32 /** 33 * Represents a snapshot of information about a Thread. 34 */ 35 class ThreadSnapshot { 36 private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess(); 37 private static final StackTraceElement[] EMPTY_STACK = new StackTraceElement[0]; 38 private static final ThreadLock[] EMPTY_LOCKS = new ThreadLock[0]; 39 private static final ThreadSnapshot NOT_ALIVE = new ThreadSnapshot(); 40 41 // filled by VM 42 private String name; 43 private int threadStatus; 44 private Thread carrierThread; 45 private StackTraceElement[] stackTrace; 46 // owned monitors 47 private ThreadLock[] locks; 48 // an object the thread is blocked/waiting on, converted to ThreadBlocker by ThreadSnapshot.of() 49 private int blockerTypeOrdinal; 50 private Object blockerObject; 51 52 // set by ThreadSnapshot.of() 53 private ThreadBlocker blocker; 54 55 private ThreadSnapshot() {} 56 57 /** 58 * Take a snapshot of a Thread to get all information about the thread. 59 * Return null if the thread is not alive. 60 * @throws UnsupportedOperationException if not supported by VM 61 */ 62 static ThreadSnapshot of(Thread thread) { 63 ThreadSnapshot snapshot; 64 if (thread.isVirtual()) { 65 do { 66 // assume unmounted 67 snapshot = JLA.supplyIfUnmounted(thread, 68 () -> create(thread, true), // unmounted and alive 69 () -> NOT_ALIVE); // not alive 70 if (snapshot == NOT_ALIVE) { 71 return null; 72 } else if (snapshot == null) { 73 // not unmounted, retry assuming mounted 74 snapshot = create(thread, false); 75 if (snapshot == null) { 76 // yield before retry 77 Thread.yield(); 78 } 79 } 80 } while (snapshot == null); 81 } else { 82 // platform thread 83 snapshot = create(thread, false); 84 if (snapshot == null) { 85 return null; // not alive 86 } 87 } 88 89 if (snapshot.stackTrace == null) { 90 snapshot.stackTrace = EMPTY_STACK; 91 } 92 if (snapshot.locks != null) { 93 Arrays.stream(snapshot.locks).forEach(ThreadLock::finishInit); 94 } else { 95 snapshot.locks = EMPTY_LOCKS; 96 } 97 if (snapshot.blockerObject != null) { 98 snapshot.blocker = new ThreadBlocker(snapshot.blockerTypeOrdinal, snapshot.blockerObject); 99 snapshot.blockerObject = null; // release 100 } 101 return snapshot; 102 } 103 104 /** 105 * Returns the thread name. 106 */ 107 String threadName() { 108 return name; 109 } 110 111 /** 112 * Returns the thread state. 113 */ 114 Thread.State threadState() { 115 return jdk.internal.misc.VM.toThreadState(threadStatus); 116 } 117 118 /** 119 * Returns the thread stack trace. 120 */ 121 StackTraceElement[] stackTrace() { 122 return stackTrace; 123 } 124 125 /** 126 * Returns the thread's parkBlocker. 127 */ 128 Object parkBlocker() { 129 return getBlocker(BlockerLockType.PARK_BLOCKER); 130 } 131 132 /** 133 * Returns the object that the thread is blocked on. 134 * @throws IllegalStateException if not in the blocked state 135 */ 136 Object blockedOn() { 137 if (threadState() != Thread.State.BLOCKED) { 138 throw new IllegalStateException(); 139 } 140 return getBlocker(BlockerLockType.WAITING_TO_LOCK); 141 } 142 143 /** 144 * Returns the object that the thread is waiting on. 145 * @throws IllegalStateException if not in the waiting state 146 */ 147 Object waitingOn() { 148 if (threadState() != Thread.State.WAITING 149 && threadState() != Thread.State.TIMED_WAITING) { 150 throw new IllegalStateException(); 151 } 152 return getBlocker(BlockerLockType.WAITING_ON); 153 } 154 155 private Object getBlocker(BlockerLockType type) { 156 return (blocker != null && blocker.type == type) ? blocker.obj : null; 157 } 158 159 /** 160 * Returns true if the thread owns any object monitors. 161 */ 162 boolean ownsMonitors() { 163 return locks.length > 0; 164 } 165 166 /** 167 * Returns the objects that the thread locked at the given depth. The stream 168 * will contain a null element for a monitor that has been eliminated. 169 */ 170 Stream<Object> ownedMonitorsAt(int depth) { 171 return Arrays.stream(locks) 172 .filter(lock -> lock.depth() == depth) 173 .map(lock -> (lock.type == OwnedLockType.LOCKED) 174 ? lock.lockObject() 175 : /*eliminated*/ null); 176 } 177 178 /** 179 * If the thread is a mounted virtual thread then return its carrier. 180 */ 181 Thread carrierThread() { 182 return carrierThread; 183 } 184 185 /** 186 * Represents information about a locking operation. 187 */ 188 private enum OwnedLockType { 189 LOCKED, 190 // Lock object is a class of the eliminated monitor 191 ELIMINATED, 192 } 193 194 private enum BlockerLockType { 195 // Park blocker 196 PARK_BLOCKER, 197 WAITING_TO_LOCK, 198 // Object.wait() 199 WAITING_ON, 200 } 201 202 /** 203 * Represents a locking operation of a thread at a specific stack depth. 204 */ 205 private static class ThreadLock { 206 private static final OwnedLockType[] lockTypeValues = OwnedLockType.values(); // cache 207 208 // set by the VM 209 private int depth; 210 private int typeOrdinal; 211 private Object obj; 212 213 // set by ThreadLock.of() 214 private OwnedLockType type; 215 216 private ThreadLock() {} 217 218 void finishInit() { 219 type = lockTypeValues[typeOrdinal]; 220 } 221 222 int depth() { 223 return depth; 224 } 225 226 OwnedLockType type() { 227 return type; 228 } 229 230 Object lockObject() { 231 if (type == OwnedLockType.ELIMINATED) { 232 // we have no lock object, lock contains lock class 233 return null; 234 } 235 return obj; 236 } 237 } 238 239 private record ThreadBlocker(BlockerLockType type, Object obj) { 240 private static final BlockerLockType[] lockTypeValues = BlockerLockType.values(); // cache 241 242 ThreadBlocker(int typeOrdinal, Object obj) { 243 this(lockTypeValues[typeOrdinal], obj); 244 } 245 } 246 247 /** 248 * Return the snapshot of the given thread if alive. If the thread is a virtual 249 * thread this method returns the snapshot if the virtual thread is mounted. If 250 * the virtual thread is unmounted then it must be suspended. 251 * @param thread the target thread 252 * @param suspendedByCaller true if a suspended unmounted virtual thread 253 * @return the snapshot, null if not alive, null if unmounted virtual and not suspended 254 */ 255 private static native ThreadSnapshot create(Thread thread, boolean suspendedByCaller); 256 }