Robert,
I think you need to start a discussion on serviceability-dev about the
diagnostic challenges in this area before proposing API changes to
java.lang.management that have wider implications and potential interop
issues. It might be that your starting point is the thread dump instead.
-Alan
On 14/11/2020 16:26, Robert Lu wrote:
When many thread try to load same class, the thread will stuck on
ClassLoader.loadClass.
At current jdk, the stacktrace by example program is:
"Thread-1" prio=5 Id=12 BLOCKED on java.lang.Object@2e817b38 owned by
"Thread-0" Id=11
at
java.base@15/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:616)
- blocked on java.lang.Object@2e817b38
at
java.base@15/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:604)
at
java.base@15/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:168)
at java.base@15/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
at app//Main.test(Main.java:19)
at app//Main$$Lambda$2/0x0000000800b8c468.run(Unknown Source)
at java.base@15/java.lang.Thread.run(Thread.java:832)
There is no way to get which class stuck the thread.
*After this patch, the stacktrace will be*:
"Thread-1" prio=5 Id=13 BLOCKED on java.lang.String@724af044 owned by
"Thread-0" Id=12
at
java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:646)
- blocked on java.lang.String@724af044 val="java.lang.String"
at
java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:634)
at
java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:182)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:519)
at app//Main2.test(Main2.java:19)
at app//Main2$$Lambda$37/0x00000001000c2a20.run(Unknown Source)
at java.base/java.lang.Thread.run(Thread.java:831)
That is, user will know which class stuck the thread, in this example, the
class is java.lang.String. It's helpful for troubleshooting.
The example program:
// Main2.javaimport java.io.PrintStream;import java.lang.management.*;
public final class Main2 {
private synchronized static void test1() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static void test() {
while (true) {
try {
Main2.class.getClassLoader().loadClass("java.lang.String");
} catch (ClassNotFoundException e) {
}
}
}
public static void main(String[] args) throws
InterruptedException, NoSuchFieldException, IllegalAccessException {
new Thread(Main2::test).start();
new Thread(Main2::test).start();
new Thread(Main2::test).start();
new Thread(Main2::test).start();
new Thread(Main2::test).start();
while (true) {
Thread.sleep(1000);
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
ThreadInfo[] infos = bean.dumpAllThreads(true, true);
for (ThreadInfo info : infos) {
System.out.println(printThreadInfo(info));
}
}
}
private static String printThreadInfo(ThreadInfo threadInfo) {
StringBuilder sb = new StringBuilder("\"" +
threadInfo.getThreadName() + "\"" +
(threadInfo.isDaemon() ? " daemon" : "") +
" prio=" + threadInfo.getPriority() +
" Id=" + threadInfo.getThreadId() + " " +
threadInfo.getThreadState());
if (threadInfo.getLockName() != null) {
sb.append(" on " + threadInfo.getLockName());
}
if (threadInfo.getLockOwnerName() != null) {
sb.append(" owned by \"" + threadInfo.getLockOwnerName() +
"\" Id=" + threadInfo.getLockOwnerId());
}
if (threadInfo.isSuspended()) {
sb.append(" (suspended)");
}
if (threadInfo.isInNative()) {
sb.append(" (in native)");
}
sb.append('\n');
int i = 0;
StackTraceElement[] stackTrace = threadInfo.getStackTrace();
for (; i < stackTrace.length; i++) {
StackTraceElement ste = stackTrace[i];
sb.append("\tat " + ste.toString());
sb.append('\n');
if (i == 0 && threadInfo.getLockInfo() != null) {
Thread.State ts = threadInfo.getThreadState();
switch (ts) {
case BLOCKED:
sb.append("\t- blocked on " +
printLockInfo(threadInfo.getLockInfo()));
sb.append('\n');
break;
case WAITING:
sb.append("\t- waiting on " +
printLockInfo(threadInfo.getLockInfo()));
sb.append('\n');
break;
case TIMED_WAITING:
sb.append("\t- waiting on " +
printLockInfo(threadInfo.getLockInfo()));
sb.append('\n');
break;
default:
}
}
for (MonitorInfo mi : threadInfo.getLockedMonitors()) {
if (mi.getLockedStackDepth() == i) {
sb.append("\t- locked " + printLockInfo(mi));
sb.append('\n');
}
}
}
if (i < stackTrace.length) {
sb.append("\t...");
sb.append('\n');
}
LockInfo[] locks = threadInfo.getLockedSynchronizers();
if (locks.length > 0) {
sb.append("\n\tNumber of locked synchronizers = " + locks.length);
sb.append('\n');
for (LockInfo li : locks) {
sb.append("\t- " + printLockInfo(li));
sb.append('\n');
}
}
sb.append('\n');
return sb.toString();
}
private static String printLockInfo(LockInfo li) {
String res = li.getClassName() + '@' +
Integer.toHexString(li.getIdentityHashCode());
// There is no getLock method in current jdk
if (li.getStringValue() != null) {
return res + " val=\"" + li.getStringValue() + "\"";
}
return res;
}
}
----------
Commit messages:
- 8253280: Use class name as class loading lock
Changes: https://github.com/openjdk/jdk/pull/104/files
Webrev: https://openjdk.github.io/cr/?repo=jdk&pr=104&range=00
Issue: https://bugs.openjdk.java.net/browse/JDK-8253280
Stats: 74 lines changed; 73 ins; 1 del
Patch: https://git.openjdk.java.net/jdk/pull/104.diff
Fetch: git fetch https://git.openjdk.java.net/jdk pull/104/head:pull/104
PR: https://git.openjdk.java.net/jdk/pull/104