RFR: 8253280: Use class name as class loading lock

Robert Lu robberphex at gmail.com
Sat Nov 14 16:26:01 UTC 2020


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


-- 
Robert Lu <robberphex at gmail.com>


More information about the core-libs-dev mailing list