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=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) There is no way to get which class stuck the thread.
**After this patch, the stacktrace will be**: "Thread-2" prio=5 Id=13 BLOCKED on java.lang.String@724af044 owned by "Thread-3" Id=14 at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:612) - blocked on java.lang.String@724af044 val="java.lang.String" at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:600) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522) at app//Main2.test(Main2.java:18) at app//Main2$$Lambda$38/0x0000000100097440.run(Unknown Source) at java.base/java.lang.Thread.run(Thread.java:832) 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: Before patch: <details> <summary>Main.java</summary> // Main.java import java.io.PrintStream; import java.lang.management.*; public final class Main { private synchronized static void test1() { while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } private static void test() { while (true) { try { Main.class.getClassLoader().loadClass("java.lang.String"); } catch (ClassNotFoundException e) { } } } public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException { new Thread(Main::test).start(); new Thread(Main::test).start(); new Thread(Main::test).start(); new Thread(Main::test).start(); new Thread(Main::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()); return res; } } </details> After patch: <details> <summary>Main2.java</summary> // Main2.java import 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; } } </details> ------------- Commit messages: - 8253280: Use class name as class loading lock Changes: https://git.openjdk.java.net/jdk/pull/104/files Webrev: https://webrevs.openjdk.java.net/?repo=jdk&pr=104&range=00 Issue: https://bugs.openjdk.java.net/browse/JDK-8253280 Stats: 73 lines in 5 files changed: 72 ins; 0 del; 1 mod 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