Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8254566

Clarify the spec of ClassLoader::getClassLoadingLock for non-parallel capable loader

XMLWordPrintable

        ADDITIONAL SYSTEM INFORMATION :
        java -version:
        openjdk version "1.8.0_265"
        OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_265-b01)
        OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.265-b01, mixed mode)
        sw_vers:
        ProductName: Mac OS X
        ProductVersion: 10.15.5
        BuildVersion: 19F101

        A DESCRIPTION OF THE PROBLEM :
        I override getClassLoadingLock not to lock instance of ClassLoader.
        However during resolve class in constant pool, OpenJDK 8u and OpenJDK master branch.
        I think JavaDoc of ClassLoader says that ClassLoader locks result of getClassLoadingLock but does not lock instance of ClassLoader.

        STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
        1. compile the attached java code. entry point class is Main.
        2. if execute without any argument, a class ('Helper2') is loaded with 'ClassLoader.loadClass', the program prints like the expected result
        3. if execute with 'useLcd' as an argument, a class ('Helper2') is loaded with ldc instruction, the program prints like the actual result.

        EXPECTED VERSUS ACTUAL BEHAVIOR :
        EXPECTED -
        lock of lock object already token
        class Helper2: Loader@1540e19d
        ACTUAL -
        lock of loader already token
        lock of lock object already token
        class Helper2: Loader@1540e19d

        ---------- BEGIN SOURCE ----------
        import java.lang.reflect.*;
        import java.net.URL;
        import java.net.URLClassLoader;
        import java.util.Timer;
        import java.util.TimerTask;
        import java.util.function.Consumer;
        import sun.misc.Unsafe;

        class Loader extends URLClassLoader {
            TimingHelper timing;
            Object lock;

            Loader(Object lock, URL[] urls, TimingHelper timing) {
                super(urls, null);
                this.timing = timing;
                this.lock = lock;
            }

            protected Class<?> findClass(String name) throws ClassNotFoundException {
                if (name.equals("Helper2")) {
                    timing.on(1);
                    timing.on(4);
                }
                return super.findClass(name);
            }

            @Override
            protected Object getClassLoadingLock(String className) {
                // to remove deadlock
                return lock;
            }
        }

        class TimingHelper {
            private int s;
            private Timer t = new Timer(true);

            public synchronized void on(int status) {
                try {
                    while (s != status - 1) {
                       wait();
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                s = status;
                notifyAll();
            }
        }

        class ChildHelper {
            public static void loadSomeClass(boolean useLdc) throws ClassNotFoundException {
                Class<?> clazz;
                if (useLdc) {
                    clazz = Helper2.class;
                } else {
                    clazz = ChildHelper.class.getClassLoader().loadClass("Helper2");
                }
                System.out.println(clazz + ": " + clazz.getClassLoader());
            }
        }

        class Helper2 {
            public static void loadSomeClass() {
                System.out.println("Helper2");
            }
        }

        class Main {
            static Unsafe unsafe;

            static {
                try {
                    Field f = Unsafe.class.getDeclaredField("theUnsafe");
                    f.setAccessible(true);
                    unsafe = (Unsafe)f.get(null);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }

            public static boolean checkLockIsNotTaken(Object obj) {
                boolean locked = false;
                try {
                    locked = unsafe.tryMonitorEnter(obj);
                } finally {
                    if (locked) unsafe.monitorExit(obj);
                }
                return !locked;
            }

            public static void main(String[] args) throws Exception {
                boolean useLdc = args.length != 0 && args[0].equals("useLdc");
                TimingHelper timing = new TimingHelper();
                URL[] urls = new URL[]{ Main.class.getProtectionDomain().getCodeSource().getLocation() };
                Object lock = new Object();
                Loader child = new Loader(lock, urls, timing);
                Method childHelpMethod = child.loadClass(ChildHelper.class.getName()).getMethod("loadSomeClass", boolean.class);
                childHelpMethod.setAccessible(true);
                Runnable childHelp = () -> {
                    try {
                        childHelpMethod.invoke(null, useLdc);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                };

                new Thread(() -> {
                    childHelp.run();
                }).start();

                timing.on(2);
                if (checkLockIsNotTaken(child)) System.out.println("lock of loader already token");
                if (checkLockIsNotTaken(lock)) System.out.println("lock of lock object already token");
                timing.on(3);
            }
        }

        ---------- END SOURCE ----------

        CUSTOMER SUBMITTED WORKAROUND :
        If register the Loader as parallel capable, the code run as I expected currently but I want to know the correct workaround.

        FREQUENCY : always


              mchung Mandy Chung
              webbuggrp Webbug Group
              Votes:
              0 Vote for this issue
              Watchers:
              9 Start watching this issue

                Created:
                Updated:
                Resolved: