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

Debugging leads to ClassLoader leaks

XMLWordPrintable

    • generic
    • generic

      ADDITIONAL SYSTEM INFORMATION :
      openjdk version "17.0.10" 2024-01-16
      OpenJDK Runtime Environment Temurin-17.0.10+7 (build 17.0.10+7)
      OpenJDK 64-Bit Server VM Temurin-17.0.10+7 (build 17.0.10+7, mixed mode)

      A DESCRIPTION OF THE PROBLEM :
      Debugging a class loaded by a ClassLoader created by a user will cause the class to be referenced by JNI global, and the ClassLoader cannot be uninstalled. Repeating this operation for many times will eventually cause MetaSpace overflow

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Debugging the source code, if I don't add any breakpoints, then the ClassLoader can be properly garbage collected, and the console prints 'ClassLoader count: 0'. However, if I add a debugging breakpoint inside the 'run' method of 'MyTask', after the program runs, the ClassLoader is not garbage collected, and the console prints 'ClassLoader count: 2'.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The ClassLoader is properly garbage collected
      ACTUAL -
      The ClassLoader is not properly garbage collected

      ---------- BEGIN SOURCE ----------
      package test;

      import java.io.FileNotFoundException;
      import java.io.IOException;
      import java.io.InputStream;
      import java.lang.ref.WeakReference;
      import java.lang.reflect.Constructor;
      import java.net.URL;
      import java.net.URLClassLoader;
      import java.security.ProtectionDomain;
      import java.time.LocalDateTime;
      import java.util.ArrayList;
      import java.util.List;

      public class TestClassLoaderLeaks {

          private static final List<WeakReference<ClassLoader>> classLoaderList = new ArrayList<>();

          private static void createClassLoader(int count) throws Exception {
              for (int i = 0; i < count; i++) {
                  ClassLoader classLoader = new MyClassLoader(new URL[]{}, i);
                  classLoaderList.add(new WeakReference<>(classLoader));

                  Class<?> clazz = Class.forName(MyTask.class.getName(), true, classLoader);
                  Constructor<?> constructor = clazz.getConstructor();
                  Runnable runnable = (Runnable) constructor.newInstance();
                  runnable.run();
              }
          }

          public static void main(String[] args) throws Exception {

              createClassLoader(2);
              gcAndPrintClassLoaderCount(100);

          }

          private static void gcAndPrintClassLoaderCount(long sleepTime) throws InterruptedException {
              System.gc();
              Thread.sleep(sleepTime);
              int clCount = 0;
              for (WeakReference<ClassLoader> reference : classLoaderList) {
                  if (reference.get() != null) {
                      clCount++;
                  }
              }
              LocalDateTime now = LocalDateTime.now();
              System.out.println(now + ": ClassLoader count: " + clCount);
          }

          public static class MyClassLoader extends URLClassLoader {

              private final int index;
              private Class<?> clazz;

              public MyClassLoader(URL[] urls, int index) {
                  super(urls);
                  this.index = index;
              }

              public int getIndex() {
                  return index;
              }

              @Override
              protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
                  if (name.equals(MyTask.class.getName())) {
                      if (clazz == null) {
                          String className = MyTask.class.getName();
                          String classPath = className.replace(".", "/") + ".class";
                          ClassLoader classLoader = TestClassLoaderLeaks.class.getClassLoader();
                          try (InputStream resourceAsStream = classLoader.getResourceAsStream(classPath)) {
                              if (resourceAsStream == null) {
                                  throw new FileNotFoundException("File " + classPath + " not found");
                              }
                              ProtectionDomain protectionDomain = new ProtectionDomain(null, null);
                              byte[] bytes = resourceAsStream.readAllBytes();
                              clazz = defineClass(className, bytes, 0, bytes.length, protectionDomain);
                          } catch (IOException e) {
                              throw new RuntimeException(e);
                          }
                      }
                      return clazz;
                  } else {
                      return super.loadClass(name, resolve);
                  }
              }
          }

          public static class MyTask implements Runnable {

              @Override
              public void run() {
                  MyClassLoader classLoader = (MyClassLoader) this.getClass().getClassLoader();
                  System.out.println("This is ClassLoader: " + classLoader.getIndex());
              }
          }

      }

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

      FREQUENCY : always


            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            6 Start watching this issue

              Created:
              Updated: