Skip to content

Commit a6ed7e9

Browse files
committed
[GR-44559] Finish ThreadMXBean implementation for Native Image.
1 parent cc4c5aa commit a6ed7e9

File tree

10 files changed

+568
-25
lines changed

10 files changed

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

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

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,22 @@
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

48+
import static com.oracle.svm.core.Uninterruptible.CALLED_FROM_UNINTERRUPTIBLE_CODE;
49+
4650
/**
4751
* This class provides a partial implementation of {@link com.sun.management.ThreadMXBean} for SVM.
4852
* <p>
@@ -55,9 +59,9 @@ public final class SubstrateThreadMXBean implements com.sun.management.ThreadMXB
5559
private final AtomicInteger peakThreadCount = new AtomicInteger(0);
5660
private final AtomicInteger threadCount = new AtomicInteger(0);
5761
private final AtomicInteger daemonThreadCount = new AtomicInteger(0);
58-
5962
private boolean allocatedMemoryEnabled;
6063
private boolean cpuTimeEnabled;
64+
private boolean contentionMonitoringEnabled;
6165

6266
@Platforms(Platform.HOSTED_ONLY.class)
6367
SubstrateThreadMXBean() {
@@ -150,45 +154,62 @@ public int getDaemonThreadCount() {
150154
return daemonThreadCount.get();
151155
}
152156

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

160170
@Override
161171
public ThreadInfo getThreadInfo(long id) {
162-
return null;
172+
return getThreadInfo(id, -1);
163173
}
164174

165175
@Override
166176
public ThreadInfo[] getThreadInfo(long[] ids) {
167-
return new ThreadInfo[0];
177+
return getThreadInfo(ids, -1);
168178
}
169179

170180
@Override
171181
public ThreadInfo getThreadInfo(long id, int maxDepth) {
172-
return null;
182+
return getThreadInfo(id, maxDepth, false, false);
183+
}
184+
185+
private ThreadInfo getThreadInfo(long id, int maxDepth, boolean lockedMonitors, boolean lockedSynchronizers) {
186+
Thread thread = getThreadById(id);
187+
return getThreadInfo(thread, maxDepth, lockedMonitors, lockedSynchronizers);
188+
}
189+
190+
private ThreadInfo getThreadInfo(Thread thread, int maxDepth, boolean lockedMonitors, boolean lockedSynchronizers) {
191+
return thread == null ? null : ThreadInfoConstructionUtils.getThreadInfo(thread, maxDepth, lockedMonitors, lockedSynchronizers);
173192
}
174193

175194
@Override
176195
public ThreadInfo[] getThreadInfo(long[] ids, int maxDepth) {
177-
return new ThreadInfo[0];
196+
return (ThreadInfo[]) Arrays.stream(ids).mapToObj(id -> getThreadInfo(id, maxDepth)).toArray();
178197
}
179198

180199
@Override
181200
public boolean isThreadContentionMonitoringSupported() {
182-
return false;
201+
return true;
183202
}
184203

185204
@Override
205+
@Uninterruptible(reason = CALLED_FROM_UNINTERRUPTIBLE_CODE, mayBeInlined = true)
186206
public boolean isThreadContentionMonitoringEnabled() {
187-
return false;
207+
return contentionMonitoringEnabled;
188208
}
189209

190210
@Override
191-
public void setThreadContentionMonitoringEnabled(boolean enable) {
211+
public void setThreadContentionMonitoringEnabled(boolean value) {
212+
contentionMonitoringEnabled = value;
192213
}
193214

194215
@Override
@@ -256,32 +277,36 @@ public void setThreadCpuTimeEnabled(boolean enable) {
256277

257278
@Override
258279
public long[] findMonitorDeadlockedThreads() {
259-
return new long[0];
280+
ThreadInfo[] threadInfos = dumpAllThreads(true, false);
281+
return ThreadMXUtils.DeadlockDetectionUtils.findDeadlockedThreads(threadInfos, true);
260282
}
261283

262284
@Override
263285
public long[] findDeadlockedThreads() {
264-
return new long[0];
286+
ThreadInfo[] threadInfos = dumpAllThreads(true, true);
287+
return ThreadMXUtils.DeadlockDetectionUtils.findDeadlockedThreads(threadInfos, false);
265288
}
266289

267290
@Override
268291
public boolean isObjectMonitorUsageSupported() {
269-
return false;
292+
return true;
270293
}
271294

272295
@Override
273296
public boolean isSynchronizerUsageSupported() {
274-
return false;
297+
return true;
275298
}
276299

277300
@Override
278301
public ThreadInfo[] getThreadInfo(long[] ids, boolean lockedMonitors, boolean lockedSynchronizers) {
279-
return new ThreadInfo[0];
302+
return Arrays.stream(ids).mapToObj(id -> getThreadInfo(id, -1, lockedMonitors, lockedSynchronizers))
303+
.toArray(ThreadInfo[]::new);
280304
}
281305

282306
@Override
283307
public ThreadInfo[] dumpAllThreads(boolean lockedMonitors, boolean lockedSynchronizers) {
284-
return new ThreadInfo[0];
308+
return Arrays.stream(PlatformThreads.getAllThreads()).map(thread -> getThreadInfo(thread, -1, lockedMonitors, lockedSynchronizers))
309+
.toArray(ThreadInfo[]::new);
285310
}
286311

287312
@Override
@@ -290,7 +315,6 @@ public long getThreadAllocatedBytes(long id) {
290315
if (!valid) {
291316
return -1;
292317
}
293-
294318
return PlatformThreads.getThreadAllocatedBytes(id);
295319
}
296320

@@ -324,8 +348,8 @@ private static void verifyThreadId(long id) {
324348
}
325349

326350
private static void verifyThreadIds(long[] ids) {
327-
for (int i = 0; i < ids.length; i++) {
328-
verifyThreadId(ids[i]);
351+
for (long id : ids) {
352+
verifyThreadId(id);
329353
}
330354
}
331355

0 commit comments

Comments
 (0)