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