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

File lock in Windows on a loaded jar due to a leak in Introspector::getBeanInfo

    Details

    • Subcomponent:
    • CPU:
      x86_64
    • OS:
      windows_10

      Description

      ADDITIONAL SYSTEM INFORMATION :
      OS: Microsoft Windows 10 pro
      Version: 10.0.18362 Number18362
      java version "12.0.2" 2019-07-16
      Java(TM) SE Runtime Environment (build 12.0.2+10)
      Java HotSpot(TM) 64-Bit Server VM (build 12.0.2+10, mixed mode, sharing)


      A DESCRIPTION OF THE PROBLEM :
      When a class is dynamically loaded from an external jar, windows locks the jar file. This lock can be removed if the classloader is garbage collected. But if we invoke the method java.beans.Introspector::getBeanInfo on a class coming from the jar file, the classloader can never become garbage collectable.
      This problem was first raised in JDK-8207331 but the scope seems greater than initially described.
      Indeed, this also happens on Windows and is not just a memory leak, it also prevents the deletion of the jar file.
      This is particularly annoying for applications that perform code updates at runtimes or for modular applications that require plugins to be loaded and unloaded.
      It should also be noted that file lock does not seem to occur on Linux.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Load a jar file containing a given class. Call java.beans.Introspector.getBeanInfo on this class. The jar file will never be able to be deleted by any technique.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Call java.beans.Introspector.flushCaches methods should clear the cache in "com.sun.beans.introspect.ClassInfo". The jar file can then be deleted.
      ACTUAL -
      The "com.sun.beans.introspect.ClassInfo" cache is not cleared. The jar file cannot be deleted.

      ---------- BEGIN SOURCE ----------
      SampleObject to be loaded;

      package test;
      module IntrospectorLeakSampleObject {
      exports test;
      }

      package test;
      public class SampleObject {
      @Override
      public String toString() {
      return "SampleObject";
      }
      }

      Loader:
      module IntrospectorLeakLoader {
      requires java.desktop;
      }

      package test;
      import java.beans.IntrospectionException;
      import java.beans.Introspector;
      import java.io.IOException;
      import java.lang.module.ModuleFinder;
      import java.lang.module.ModuleReference;
      import java.lang.reflect.InvocationTargetException;
      import java.nio.file.FileSystemException;
      import java.nio.file.Files;
      import java.nio.file.Path;
      import java.nio.file.Paths;
      import java.nio.file.StandardCopyOption;
      import java.util.Set;
      public class LeakTest {
      public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, IntrospectionException {
      Path testedJar = Paths.get("SampleTest.jar");
      Files.copy(Paths.get("Sample.jar"), testedJar, StandardCopyOption.REPLACE_EXISTING);
      leakTest(testedJar);
      while(true) {
      System.gc();
      try {
      Files.delete(testedJar);
      System.out.println("Leak test passed!!!");
      break;
      }catch(FileSystemException e) {
      System.err.println("Leak test failed...");
      }
      Thread.sleep(1000);
      }
      }

      private static void leakTest(Path testedJar) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, IntrospectionException {
      ModuleLayer parent = ModuleLayer.boot();
      ModuleFinder finder = ModuleFinder.of(testedJar);
      ModuleReference a = finder.findAll().iterator().next();
      String moduleName = a.descriptor().name();
      ModuleLayer moduleLayer = parent.defineModulesWithOneLoader(parent.configuration().resolve(finder, ModuleFinder.of(), Set.of(moduleName)), ClassLoader.getSystemClassLoader());
      ClassLoader classLoader = moduleLayer.findLoader(moduleName);
      Class<?> c = Class.forName("test.SampleObject", true, classLoader);
      System.out.println("Text from loaded object: " + c.getConstructor().newInstance().toString());
      Introspector.getBeanInfo(c);
      Introspector.flushFromCaches(c);
      Introspector.flushCaches();
      }
      }
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      No obvious workaround. Since the introduction of the modules in java, we can't use reflection on "com.sun.beans.beans.beans.introspect.ClassInfo" without having an IllegalAccessException.

      FREQUENCY : always


        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                serb Sergey Bylokhov
                Reporter:
                webbuggrp Webbug Group
              • Votes:
                0 Vote for this issue
                Watchers:
                3 Start watching this issue

                Dates

                • Created:
                  Updated: