Skip to content

Commit 21d598c

Browse files
committed
[GR-44559] Finish ThreadMXBean implementation for Native Image.
1 parent f0e8373 commit 21d598c

File tree

10 files changed

+505
-26
lines changed

10 files changed

+505
-26
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.oracle.svm.core.jdk;
2+
3+
import com.oracle.svm.core.annotate.Alias;
4+
import com.oracle.svm.core.annotate.TargetClass;
5+
6+
@SuppressWarnings({"unused"})
7+
@TargetClass(java.lang.management.ThreadInfo.class)
8+
final class Target_java_lang_management_ThreadInfo {
9+
10+
@Alias
11+
public Target_java_lang_management_ThreadInfo(Thread t, int state, Object lockObj, Thread lockOwner,
12+
long blockedCount, long blockedTime,
13+
long waitedCount, long waitedTime,
14+
StackTraceElement[] stackTrace,
15+
Object[] monitors,
16+
int[] stackDepths,
17+
Object[] synchronizers) {
18+
}
19+
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/*
2+
* Copyright (c) 2015, 2017, 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 com.oracle.svm.core.jdk;
26+
27+
import com.oracle.svm.core.jdk.management.SubstrateThreadMXBean;
28+
import com.oracle.svm.core.locks.Target_java_util_concurrent_locks_AbstractOwnableSynchronizer;
29+
import com.oracle.svm.core.monitor.JavaMonitor;
30+
import com.oracle.svm.core.thread.JavaThreads.JMXMonitoring;
31+
import com.oracle.svm.core.thread.PlatformThreads;
32+
import com.oracle.svm.core.thread.VMThreads;
33+
import org.graalvm.collections.EconomicSet;
34+
35+
import java.lang.management.ThreadInfo;
36+
import java.util.Arrays;
37+
import java.util.HashSet;
38+
import java.util.concurrent.locks.AbstractOwnableSynchronizer;
39+
import java.util.concurrent.locks.LockSupport;
40+
import java.util.concurrent.locks.ReentrantLock;
41+
42+
/**
43+
* Utils to support {@link SubstrateThreadMXBean} for providing threading information for JMX
44+
* support. Include the {@link ThreadInfo} constructing utils, and deadlock detection utils.
45+
*/
46+
public class ThreadMXUtils {
47+
public static class ThreadInfoConstructionUtils {
48+
49+
private static StackTraceElement[] getStackTrace(Thread thread, int maxDepth) {
50+
return maxDepth == -1 || maxDepth >= thread.getStackTrace().length ? thread.getStackTrace() : Arrays.copyOfRange(thread.getStackTrace(), 0, maxDepth);
51+
}
52+
53+
private static int getThreadState(Thread thread) {
54+
int state = PlatformThreads.getThreadStatus(thread);
55+
boolean inNative = VMThreads.StatusSupport
56+
.getStatusVolatile(PlatformThreads.getIsolateThreadUnsafe(thread)) == VMThreads.StatusSupport.STATUS_IN_NATIVE;
57+
if (inNative) {
58+
// set the JMM thread state native flag to true:
59+
state |= 0x00400000;
60+
}
61+
return state;
62+
}
63+
64+
private record Blocker(Object blockObject, Thread ownerThread) {
65+
}
66+
67+
private static Blocker getBlockerInfo(Thread thread) {
68+
Object blocker = LockSupport.getBlocker(thread);
69+
70+
if (blocker instanceof JavaMonitor javaMonitor) {
71+
return new Blocker(
72+
javaMonitor.getBlockedObject(),
73+
SubstrateThreadMXBean.getThreadById(javaMonitor.getOwnerThreadId()));
74+
} else if (blocker instanceof AbstractOwnableSynchronizer synchronizer) {
75+
return new Blocker(synchronizer, Target_java_util_concurrent_locks_AbstractOwnableSynchronizer.class
76+
.cast(synchronizer).getExclusiveOwnerThread());
77+
}
78+
return new Blocker(blocker, null);
79+
}
80+
81+
private static Object[] getLockedSynchronizers(Thread thread) {
82+
EconomicSet<AbstractOwnableSynchronizer> locks = JMXMonitoring.getThreadLocks(thread);
83+
Object[] lockObjects = new Object[locks != null ? locks.size() : 0];
84+
for (int i = 0; i < lockObjects.length; i++) {
85+
lockObjects[i] = locks.iterator().next();
86+
}
87+
return lockObjects;
88+
}
89+
90+
private record LockedMonitors(Object[] monitorObjects, int[] monitorDepths) {
91+
}
92+
93+
private static LockedMonitors getLockedMonitors(Thread thread) {
94+
EconomicSet<JavaMonitor> monitors = JMXMonitoring.getThreadMonitors(thread);
95+
Object[] monitorObjects = new Object[monitors != null ? monitors.size() : 0];
96+
int[] monitorDepths = new int[monitorObjects.length];
97+
for (int i = 0; i < monitorObjects.length; i++) {
98+
JavaMonitor javaMonitor = monitors.iterator().next();
99+
monitorObjects[i] = javaMonitor.getBlockedObject();
100+
monitorDepths[i] = javaMonitor.getDepth();
101+
}
102+
return new LockedMonitors(monitorObjects, monitorDepths);
103+
}
104+
105+
public static ThreadInfo getThreadInfo(Thread thread, int maxDepth,
106+
boolean withLockedMonitors, boolean withLockedSynchronizers) {
107+
assert thread != null;
108+
Blocker blocker = getBlockerInfo(thread);
109+
LockedMonitors lockedMonitors = getLockedMonitors(thread);
110+
111+
Target_java_lang_management_ThreadInfo targetThreadInfo = new Target_java_lang_management_ThreadInfo(
112+
thread,
113+
getThreadState(thread),
114+
blocker.blockObject,
115+
blocker.ownerThread,
116+
JMXMonitoring.getThreadTotalBlockedCount(thread),
117+
JMXMonitoring.getThreadTotalBlockedTime(thread),
118+
JMXMonitoring.getThreadTotalWaitedCount(thread),
119+
JMXMonitoring.getThreadTotalWaitedTime(thread),
120+
getStackTrace(thread, maxDepth),
121+
withLockedMonitors ? lockedMonitors.monitorObjects : new Object[0],
122+
withLockedMonitors ? lockedMonitors.monitorDepths : new int[0],
123+
withLockedSynchronizers ? getLockedSynchronizers(thread) : new Object[0]);
124+
return ThreadInfo.class.cast(targetThreadInfo);
125+
}
126+
}
127+
128+
public static class DeadlockDetectionUtils {
129+
private static final ReentrantLock lock = new ReentrantLock();
130+
private static final HashSet<Long> deadlocked = new HashSet<>();
131+
private static final HashSet<Long> chain = new HashSet<>();
132+
private static ThreadInfo[] allThreadInfos = new ThreadInfo[0];
133+
134+
/**
135+
* Returns an array of thread ids of blocked threads within some given array of ThreadInfo.
136+
*
137+
* @param threadInfos array of ThreadInfo for the threads among which the deadlocks should
138+
* be detected
139+
* @param byMonitorOnly true if we are interested only in the deadlocks blocked exclusively
140+
* on monitors
141+
* @return array containing thread ids of deadlocked threads
142+
*/
143+
public static long[] findDeadlockedThreads(ThreadInfo[] threadInfos, boolean byMonitorOnly) {
144+
lock.lock();
145+
try {
146+
allThreadInfos = threadInfos;
147+
deadlocked.clear();
148+
chain.clear();
149+
150+
Arrays.stream(allThreadInfos)
151+
.filter(threadInfo -> !deadlocked.contains(threadInfo.getThreadId()))
152+
.forEach(threadInfo -> checkBlocker(threadInfo, byMonitorOnly));
153+
return deadlocked.stream().mapToLong(threadId -> threadId).toArray();
154+
} finally {
155+
lock.unlock();
156+
}
157+
}
158+
159+
private static void checkBlocker(ThreadInfo currentThreadInfo, boolean byMonitorOnly) {
160+
if (chain.contains(currentThreadInfo.getThreadId())) {
161+
releaseCurrentChain(byMonitorOnly);
162+
} else {
163+
chain.add(currentThreadInfo.getThreadId());
164+
Arrays.stream(allThreadInfos)
165+
.filter(threadInfo -> threadInfo.getThreadId() == currentThreadInfo.getLockOwnerId() &&
166+
threadInfo.getLockInfo() != null)
167+
.findAny()
168+
.ifPresentOrElse(threadInfo -> checkBlocker(threadInfo, byMonitorOnly),
169+
chain::clear);
170+
}
171+
}
172+
173+
private static void releaseCurrentChain(boolean byMonitorOnly) {
174+
if (!byMonitorOnly || chain.stream().allMatch(DeadlockDetectionUtils::isBlockedByMonitor)) {
175+
deadlocked.addAll(chain);
176+
}
177+
chain.clear();
178+
}
179+
180+
/**
181+
* Anything that is deadlocked can be blocked either by monitor (the object related to
182+
* JavaMonitor), or a lock (anything under AbstractOwnableSynchronizer).
183+
*
184+
* @return true if provided thread is blocked by a monitor
185+
*/
186+
private static boolean isBlockedByMonitor(long threadId) {
187+
return LockSupport.getBlocker(SubstrateThreadMXBean.getThreadById(threadId)) instanceof JavaMonitor;
188+
}
189+
}
190+
}

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/management/SubstrateThreadMXBean.java

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,17 @@
3131

3232
import javax.management.ObjectName;
3333

34+
import com.oracle.svm.core.jdk.ThreadMXUtils;
35+
import com.oracle.svm.core.thread.ThreadCpuTimeSupport;
3436
import org.graalvm.nativeimage.ImageSingletons;
3537
import org.graalvm.nativeimage.Platform;
3638
import org.graalvm.nativeimage.Platforms;
3739

3840
import com.oracle.svm.core.Uninterruptible;
3941
import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicInteger;
4042
import com.oracle.svm.core.jdk.UninterruptibleUtils.AtomicLong;
43+
import com.oracle.svm.core.jdk.ThreadMXUtils.ThreadInfoConstructionUtils;
4144
import com.oracle.svm.core.thread.PlatformThreads;
42-
import com.oracle.svm.core.thread.ThreadCpuTimeSupport;
4345

4446
import sun.management.Util;
4547

@@ -55,9 +57,9 @@ public final class SubstrateThreadMXBean implements com.sun.management.ThreadMXB
5557
private final AtomicInteger peakThreadCount = new AtomicInteger(0);
5658
private final AtomicInteger threadCount = new AtomicInteger(0);
5759
private final AtomicInteger daemonThreadCount = new AtomicInteger(0);
58-
5960
private boolean allocatedMemoryEnabled;
6061
private boolean cpuTimeEnabled;
62+
private boolean contentionMonitoringEnabled;
6163

6264
@Platforms(Platform.HOSTED_ONLY.class)
6365
SubstrateThreadMXBean() {
@@ -150,45 +152,60 @@ public int getDaemonThreadCount() {
150152
return daemonThreadCount.get();
151153
}
152154

153-
/* All remaining methods are unsupported on Substrate VM. */
154-
155155
@Override
156156
public long[] getAllThreadIds() {
157-
return new long[0];
157+
return Arrays.stream(PlatformThreads.getAllThreads())
158+
.mapToLong(Thread::threadId)
159+
.toArray();
160+
}
161+
162+
public static Thread getThreadById(long id) {
163+
return Arrays.stream(PlatformThreads.getAllThreads())
164+
.filter(thread -> thread.threadId() == id)
165+
.findAny().orElse(null);
158166
}
159167

160168
@Override
161169
public ThreadInfo getThreadInfo(long id) {
162-
return null;
170+
return getThreadInfo(id, -1);
163171
}
164172

165173
@Override
166174
public ThreadInfo[] getThreadInfo(long[] ids) {
167-
return new ThreadInfo[0];
175+
return (ThreadInfo[]) Arrays.stream(ids).mapToObj(this::getThreadInfo).toArray();
168176
}
169177

170178
@Override
171179
public ThreadInfo getThreadInfo(long id, int maxDepth) {
172-
return null;
180+
return getThreadInfo(id, maxDepth, false, false);
181+
}
182+
183+
private ThreadInfo getThreadInfo(long id, int maxDepth, boolean lockedMonitors, boolean lockedSynchronizers) {
184+
Thread thread = getThreadById(id);
185+
if (thread == null)
186+
return null;
187+
return ThreadInfoConstructionUtils.getThreadInfo(thread, maxDepth, lockedMonitors, lockedSynchronizers);
188+
173189
}
174190

175191
@Override
176192
public ThreadInfo[] getThreadInfo(long[] ids, int maxDepth) {
177-
return new ThreadInfo[0];
193+
return (ThreadInfo[]) Arrays.stream(ids).mapToObj(id -> getThreadInfo(id, maxDepth)).toArray();
178194
}
179195

180196
@Override
181197
public boolean isThreadContentionMonitoringSupported() {
182-
return false;
198+
return true;
183199
}
184200

185201
@Override
186202
public boolean isThreadContentionMonitoringEnabled() {
187-
return false;
203+
return contentionMonitoringEnabled;
188204
}
189205

190206
@Override
191-
public void setThreadContentionMonitoringEnabled(boolean enable) {
207+
public void setThreadContentionMonitoringEnabled(boolean value) {
208+
contentionMonitoringEnabled = value;
192209
}
193210

194211
@Override
@@ -256,32 +273,35 @@ public void setThreadCpuTimeEnabled(boolean enable) {
256273

257274
@Override
258275
public long[] findMonitorDeadlockedThreads() {
259-
return new long[0];
276+
ThreadInfo[] threadInfos = dumpAllThreads(true, false);
277+
return ThreadMXUtils.DeadlockDetectionUtils.findDeadlockedThreads(threadInfos, true);
260278
}
261279

262280
@Override
263281
public long[] findDeadlockedThreads() {
264-
return new long[0];
282+
ThreadInfo[] threadInfos = dumpAllThreads(true, true);
283+
return ThreadMXUtils.DeadlockDetectionUtils.findDeadlockedThreads(threadInfos, false);
265284
}
266285

267286
@Override
268287
public boolean isObjectMonitorUsageSupported() {
269-
return false;
288+
return true;
270289
}
271290

272291
@Override
273292
public boolean isSynchronizerUsageSupported() {
274-
return false;
293+
return true;
275294
}
276295

277296
@Override
278297
public ThreadInfo[] getThreadInfo(long[] ids, boolean lockedMonitors, boolean lockedSynchronizers) {
279-
return new ThreadInfo[0];
298+
return Arrays.stream(ids).mapToObj(id -> getThreadInfo(id, -1, lockedMonitors, lockedSynchronizers))
299+
.toArray(ThreadInfo[]::new);
280300
}
281301

282302
@Override
283303
public ThreadInfo[] dumpAllThreads(boolean lockedMonitors, boolean lockedSynchronizers) {
284-
return new ThreadInfo[0];
304+
return getThreadInfo(getAllThreadIds(), lockedMonitors, lockedSynchronizers);
285305
}
286306

287307
@Override
@@ -290,7 +310,6 @@ public long getThreadAllocatedBytes(long id) {
290310
if (!valid) {
291311
return -1;
292312
}
293-
294313
return PlatformThreads.getThreadAllocatedBytes(id);
295314
}
296315

@@ -324,8 +343,8 @@ private static void verifyThreadId(long id) {
324343
}
325344

326345
private static void verifyThreadIds(long[] ids) {
327-
for (int i = 0; i < ids.length; i++) {
328-
verifyThreadId(ids[i]);
346+
for (long id : ids) {
347+
verifyThreadId(id);
329348
}
330349
}
331350

0 commit comments

Comments
 (0)