RFR: 8253280: Use class name as class loading lock

Alan Bateman Alan.Bateman at oracle.com
Sun Nov 15 07:45:59 UTC 2020


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 at 2e817b38 owned by
> "Thread-0" Id=11
>          at java.base at 15/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:616)
>          -  blocked on java.lang.Object at 2e817b38
>          at java.base at 15/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:604)
>          at java.base at 15/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:168)
>          at java.base at 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 at 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 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)
>
> 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
>
>



More information about the core-libs-dev mailing list