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

Clarify the spec of ClassLoader::loadClass about the lock acquired by non-parallel capable loader

    XMLWordPrintable

    Details

      Description

      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


        Attachments

          Activity

            People

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

              Dates

              Created:
              Updated: