Skip to content

Commit 75984b5

Browse files
committed
[GR-44559] Finish ThreadMXBean implementation for Native Image.
1 parent d9b8d30 commit 75984b5

File tree

10 files changed

+549
-24
lines changed

10 files changed

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

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

Lines changed: 42 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,61 @@ 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 getThreadInfo(ids, -1);
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+
return getThreadInfo(thread, maxDepth, lockedMonitors, lockedSynchronizers);
186+
}
187+
188+
private ThreadInfo getThreadInfo(Thread thread, int maxDepth, boolean lockedMonitors, boolean lockedSynchronizers) {
189+
return thread == null ? null : ThreadInfoConstructionUtils.getThreadInfo(thread, maxDepth, lockedMonitors, lockedSynchronizers);
173190
}
174191

175192
@Override
176193
public ThreadInfo[] getThreadInfo(long[] ids, int maxDepth) {
177-
return new ThreadInfo[0];
194+
return (ThreadInfo[]) Arrays.stream(ids).mapToObj(id -> getThreadInfo(id, maxDepth)).toArray();
178195
}
179196

180197
@Override
181198
public boolean isThreadContentionMonitoringSupported() {
182-
return false;
199+
return true;
183200
}
184201

185202
@Override
186203
public boolean isThreadContentionMonitoringEnabled() {
187-
return false;
204+
return contentionMonitoringEnabled;
188205
}
189206

190207
@Override
191-
public void setThreadContentionMonitoringEnabled(boolean enable) {
208+
public void setThreadContentionMonitoringEnabled(boolean value) {
209+
contentionMonitoringEnabled = value;
192210
}
193211

194212
@Override
@@ -256,32 +274,36 @@ public void setThreadCpuTimeEnabled(boolean enable) {
256274

257275
@Override
258276
public long[] findMonitorDeadlockedThreads() {
259-
return new long[0];
277+
ThreadInfo[] threadInfos = dumpAllThreads(true, false);
278+
return ThreadMXUtils.DeadlockDetectionUtils.findDeadlockedThreads(threadInfos, true);
260279
}
261280

262281
@Override
263282
public long[] findDeadlockedThreads() {
264-
return new long[0];
283+
ThreadInfo[] threadInfos = dumpAllThreads(true, true);
284+
return ThreadMXUtils.DeadlockDetectionUtils.findDeadlockedThreads(threadInfos, false);
265285
}
266286

267287
@Override
268288
public boolean isObjectMonitorUsageSupported() {
269-
return false;
289+
return true;
270290
}
271291

272292
@Override
273293
public boolean isSynchronizerUsageSupported() {
274-
return false;
294+
return true;
275295
}
276296

277297
@Override
278298
public ThreadInfo[] getThreadInfo(long[] ids, boolean lockedMonitors, boolean lockedSynchronizers) {
279-
return new ThreadInfo[0];
299+
return Arrays.stream(ids).mapToObj(id -> getThreadInfo(id, -1, lockedMonitors, lockedSynchronizers))
300+
.toArray(ThreadInfo[]::new);
280301
}
281302

282303
@Override
283304
public ThreadInfo[] dumpAllThreads(boolean lockedMonitors, boolean lockedSynchronizers) {
284-
return new ThreadInfo[0];
305+
return Arrays.stream(PlatformThreads.getAllThreads()).map(thread -> getThreadInfo(thread, -1, lockedMonitors, lockedSynchronizers))
306+
.toArray(ThreadInfo[]::new);
285307
}
286308

287309
@Override
@@ -290,7 +312,6 @@ public long getThreadAllocatedBytes(long id) {
290312
if (!valid) {
291313
return -1;
292314
}
293-
294315
return PlatformThreads.getThreadAllocatedBytes(id);
295316
}
296317

@@ -324,8 +345,8 @@ private static void verifyThreadId(long id) {
324345
}
325346

326347
private static void verifyThreadIds(long[] ids) {
327-
for (int i = 0; i < ids.length; i++) {
328-
verifyThreadId(ids[i]);
348+
for (long id : ids) {
349+
verifyThreadId(id);
329350
}
330351
}
331352

0 commit comments

Comments
 (0)