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