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 private String name; 38 private int threadStatus; 39 private StackTraceElement[] stackTrace; 40 private ThreadLock[] locks; 41 42 // called by the VM 43 private ThreadSnapshot(StackTraceElement[] stackTrace, 44 ThreadLock[] locks, 45 String name, 46 int threadStatus) { 47 this.stackTrace = stackTrace; 48 this.locks = locks; 49 this.name = name; 50 this.threadStatus = threadStatus; 51 } 52 53 /** 54 * Take a snapshot of a Thread to get all information about the thread. 55 */ 56 static ThreadSnapshot of(Thread thread) { 57 ThreadSnapshot snapshot = create(thread, true); 58 if (snapshot.stackTrace == null) { 59 snapshot.stackTrace = EMPTY_STACK; 60 } 61 if (snapshot.locks == null) { 62 snapshot.locks = EMPTY_LOCKS; 63 } 64 return snapshot; 65 } 66 67 /** 68 * Returns the thread name. 69 */ 70 String threadName() { 71 return name; 72 } 73 74 /** 75 * Returns the thread state. 76 */ 77 Thread.State threadState() { 78 // is this valid for virtual threads 79 return jdk.internal.misc.VM.toThreadState(threadStatus); 80 } 81 82 /** 83 * Returns the thread stack trace. 84 */ 85 StackTraceElement[] stackTrace() { 86 return stackTrace; 87 } 88 89 /** 90 * Returns the thread's parkBlocker. 91 */ 92 Object parkBlocker() { 93 return findLockObject(0, LockType.PARKING_TO_WAIT) 94 .findAny() 95 .orElse(null); 96 } 97 98 /** 99 * Returns the object that the thread is blocked on. 100 * @throws IllegalStateException if not in the blocked state 101 */ 102 Object blockedOn() { 103 if (threadState() != Thread.State.BLOCKED) { 104 throw new IllegalStateException(); 105 } 106 return findLockObject(0, LockType.WAITING_TO_LOCK) 107 .findAny() 108 .orElse(null); 109 } 110 111 /** 112 * Returns the object that the thread is waiting on. 113 * @throws IllegalStateException if not in the waiting state 114 */ 115 Object waitingOn() { 116 if (threadState() != Thread.State.WAITING 117 && threadState() != Thread.State.TIMED_WAITING) { 118 throw new IllegalStateException(); 119 } 120 return findLockObject(0, LockType.WAITING_ON) 121 .findAny() 122 .orElse(null); 123 } 124 125 /** 126 * Returns true if the thread owns any object monitors. 127 */ 128 boolean ownsMonitors() { 129 return Arrays.stream(locks) 130 .anyMatch(lock -> lock.type == LockType.LOCKED); 131 } 132 133 /** 134 * Returns the objects that the thread locked at the given depth. 135 */ 136 Stream<Object> ownedMonitorsAt(int depth) { 137 return findLockObject(depth, LockType.LOCKED); 138 } 139 140 private Stream<Object> findLockObject(int depth, LockType type) { 141 return Arrays.stream(locks) 142 .filter(lock -> lock.depth == depth 143 && lock.type() == type 144 && lock.lockObject() != null) 145 .map(ThreadLock::lockObject); 146 } 147 148 /** 149 * Represents information about a locking operation. 150 */ 151 private enum LockType { 152 // Park blocker 153 PARKING_TO_WAIT, 154 // Lock object is a class of the eliminated monitor 155 ELEMINATED_SCALAR_REPLACED, 156 ELEMINATED_MONITOR, 157 LOCKED, 158 WAITING_TO_LOCK, 159 WAITING_ON, 160 WAITING_TO_RELOCK, 161 // No corresponding stack frame, depth is always == -1 162 OWNABLE_SYNCHRONIZER 163 } 164 165 /** 166 * Represents a locking operation of a thread at a specific stack depth. 167 */ 168 private record ThreadLock(int depth, LockType type, Object obj) { 169 private static final LockType[] lockTypeValues = LockType.values(); // cache 170 171 // called by the VM 172 private ThreadLock(int depth, int typeOrdinal, Object obj) { 173 this(depth, lockTypeValues[typeOrdinal], obj); 174 } 175 176 Object lockObject() { 177 if (type == LockType.ELEMINATED_SCALAR_REPLACED) { 178 // we have no lock object, lock contains lock class 179 return null; 180 } 181 return obj; 182 } 183 } 184 185 private static native ThreadSnapshot create(Thread thread, boolean withLocks); 186 }