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