RFR: 8253280: Use class name as class loading lock

Mandy Chung mchung at openjdk.java.net
Sat Nov 14 16:06:02 UTC 2020


On Thu, 10 Sep 2020 05:25:43 GMT, Robert LU <github.com+1926185+RobberPhex at openjdk.org> 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=13 BLOCKED on java.lang.String at 724af044 owned by "Thread-0" Id=12
>         at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:646)
>         -  blocked on java.lang.String at 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 at 724af044 owned by "Thread-3" Id=14
> 	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:612)
> 	-  blocked on java.lang.String at 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>

This patch proposes to add two public APIs `java.lang.management.LockInfo::getLock`
and the new `java.lang.management.MonitorInfo` constructor taking the lock object.
`java.lang.management.MonitorInfo` and `LockInfo` by design do not expose
the lock object for remote monitoring reason (may not be serializable) and
for security reason.   The API instead provides the class name and identity hash
code of the lock object serving. as an unique ID.

I read JDK-8253280 as improving the diagnosability of ClassLoader-specific lock
returned from `ClassLoader::getClassLoadingLock` for example improving
the VM thread dump by control-break to special case the lock object for class
loading operation.   `LockInfo` is not specific for ClassLoader and so it does 
not seem the appropriate place to embed this information.


For example
the class loading lock is a new ClassLoader$Lock class and VM will perhaps call Lock::toString
(or print a specific field) in the thread dump output.

The attached program uses `java.lang.management.ThreadMXBean` to get a thread dump
and prints the output.

-------------

PR: https://git.openjdk.java.net/jdk/pull/104


More information about the core-libs-dev mailing list