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