JDK-8151486 : Class.forName causes memory leak
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang
  • Affected Version: 8u25,9
  • Priority: P2
  • Status: Closed
  • Resolution: Fixed
  • OS: windows_7
  • CPU: x86_64
  • Submitted: 2016-03-07
  • Updated: 2019-10-04
  • Resolved: 2016-10-07
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9 Other
8u231Fixed 9 b140Fixed openjdk8u232Fixed
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_73"
Java(TM) SE Runtime Environment (build 1.8.0_73-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.73-b02, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [version 6.1.7601]


A DESCRIPTION OF THE PROBLEM :
Class.forName use with Classloader.getSystemClassLoader and policy and security manager activated provoke a memory leak.

REGRESSION.  Last worked in version 8u66

ADDITIONAL REGRESSION INFORMATION: 
Don't know exactly the version.
On Mac OS X it works with 1.6.0_65 and 1.8.0_05.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile the 2 classes with a maven project
java -jar target/classfornameleak-0.0.1-SNAPSHOT.jar

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
classloader name: leaked classloader
OK no bug
ACTUAL -
classloader name: leaked classloader
Not yet GC
Not yet GC
Not yet GC
Not yet GC
Not yet GC
...

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
package classfornameleak;

import java.util.List;


public class ClassForName implements Runnable {

    public void run() {
        System.out.println("classloader name: " + ClassForName.class.getClassLoader());
        try {
            Class.forName(List.class.getName(), false, ClassLoader.getSystemClassLoader());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

}


-----------


package classfornameleak;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.Permission;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;

public class ClassForNameLeak {

    public static WeakReference<ClassLoader> init() throws Exception {
        System.setSecurityManager(new SecurityManager() {
            @Override
            public void checkPermission(final Permission perm) {
                return;
            }
        });
        Policy.setPolicy(new Policy() {
            @Override
            public boolean implies(final ProtectionDomain domain, final Permission permission) {
                return true;
            }
        });

        URL resource = ClassForNameLeak.class.getResource("ClassForName.class");
        File jarFile = File.createTempFile("cfn", ".jar");
        JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(jarFile));
        jarFile.deleteOnExit();

        ZipEntry zipEntry = new ZipEntry("classfornameleak/");
        jarOutputStream.putNextEntry(zipEntry);

        zipEntry = new ZipEntry("classfornameleak/ClassForName.class");
        byte[] buffer = new byte[1024];
        InputStream openStream = resource.openStream();

        jarOutputStream.putNextEntry(zipEntry);
        while (true) {
            int count = openStream.read(buffer);
            if (count == -1) {
                break;
            }
            jarOutputStream.write(buffer, 0, count);
        }
        jarOutputStream.closeEntry();
        jarOutputStream.close();

        ClassLoader classLoader = new URLClassLoader(new URL[] {jarFile.toURI().toURL()}) {

            @Override
            public Class<?> loadClass(final String name) throws ClassNotFoundException {
                if (ClassForName.class.getName().equals(name)) {
                    return findClass(name);
                }
                return super.loadClass(name);
            }

            @Override
            public String toString() {
                return "leaked classloader";
            }
        };

        Class<?> loadClass = classLoader.loadClass(ClassForName.class.getName());
        ((Runnable) loadClass.newInstance()).run();
        return new WeakReference<ClassLoader>(classLoader);
    }

    public static void main(final String[] args) throws Exception {
        WeakReference<ClassLoader> weakReference = init();

        for (int i = 0; i < 10; i++) {
            System.gc();
        }
        while (weakReference.get() != null) {
            System.out.println("Not yet GC");
            System.gc();
            Thread.sleep(100);
        }

        System.out.println("OK no bug");

    }

}


------------------

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>classfornameleak</groupId>
  <artifactId>classfornameleak</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
        <configuration>
          <archive>
            <manifest>
              <mainClass>classfornameleak.ClassForNameLeak</mainClass>
            </manifest>
          </archive>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Not found


Comments
Fix Request (8u) This fix resolves the memory leak by cleaning up the unused code. Patch applies with the usual reshufflings to 8u, plus the test adjustments. New test fails without the fix, passes with it. 8u RFR: https://mail.openjdk.java.net/pipermail/jdk8u-dev/2019-July/009756.html
02-07-2019

Verified in 9/144
23-11-2016

The leak occurs because of the following: Class.forName0() is passed the "caller" class, ClassForName. JVM_FindClassFromCaller will "find a class with this name (java.util.List) in this loader (the system classloader), using the caller's (ClassForName's) protection domain. A ProtectionDomain is created for ClassForName, with ProtectionDomain.classloader referring to LeakedClassLoader. This PD is passed to ClassLoader.checkPackageAccess(), and is added to 'domains' of the system classloader (ClassLoader.java line 643). Thus, the system classloader holds a reference to the user's classloader via 'domains'. Nothing is ever removed from 'domains'. In fact, besides being added to, I found no other uses of 'domains' - not in library code, not in hotspot. (From my research, it's questionable if 'domains' was ever used). Removal of 'domains' fixes the leak, and cleans out a little bit of unused code. 'domains' is not needed or intended to keep the PD alive - the VM maintains its own cache of these initiating PD (for performance), though does not prevent collection.
05-10-2016

Brent, try running -XX:+TraceProtectionDomain with the before/after JDK. Since the class is being loaded with the protection domain in 8u25b01, it might be added to the system dictionary and made a strong root.
02-06-2016

Looks like this was introduced in JDK 9b36.
26-05-2016

HeapWalker thinks the ClassLoader is being held by ProtectionDomain, which makes this look similar to JDK-8085903, however this issue persists after 9b102.
24-05-2016

Looks like this started happening in 8u25b01.
24-05-2016

Attached test case executed on : JDK 8 - Pass JDK 8u11 - Pass JDK 8u20 - Pass JDK 8u25 - Fail JDK 8u33 - Fail JDK 8u74 - Fail JDK 1.9.0 - Fail
09-03-2016