JDK-8206240 : java.lang.Class.newInstance() is causing caller to leak
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang:reflect
  • Affected Version: 8,10,11
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: linux
  • CPU: x86_64
  • Submitted: 2018-06-29
  • Updated: 2019-05-25
  • Resolved: 2018-10-04
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 12
12 b15Fixed
Related Reports
Duplicate :  
Duplicate :  
Relates :  
Relates :  
Description
A DESCRIPTION OF THE PROBLEM :
This is a similar bug to: https://bugs.openjdk.java.net/browse/JDK-8202113
Which I've reported, and I'm sorry to report we've missed one case which needs to be fixed also

Only now its with the Class.newInstance() method which caches the caller classloader in Class.newInstanceCallerCache variable

I would suggest to make that cache WeakReference so it could be freed if needed.
Since the WeakReference is to caller Class, then it won't be collected as long as the caller class ClassLoader isn't collected, so its safe to say the weak reference will stay as long as the caller ClassLoader has a path to GC.

Something like this in ./src/java.base/share/classes/java/lang/Class.java newInstance() method:

        WeakReference<Class<?>> cachedCallerRef = newInstanceCallerCache;
        Class<?> cachedCaller = cachedCallerRef != null ? cachedCallerRef.get() : null;
        if (cachedCaller != caller) {
            int modifiers = tmpConstructor.getModifiers();
            Reflection.ensureMemberAccess(caller, this, this, modifiers);
            newInstanceCallerCache = new WeakReference<>(caller);
        }

will do.

Same as previous bug reported, this bug also exists in Java8 and below, but was almost non-existance because of the `Reflection.quickCheckMemberAccess` which avoided caching the `newInstanceCallerCache`
But since Java9 that code was removed (revision 9d0388c6b336) it always happens under Java9+

As for unit-test, it should be added to ReflectionCallerCacheTest.java


REGRESSION : Last worked in version 8u172

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Call java.lang.String.class.newInstance() under a class loader, and see that it can't freed

Or, add another case to unit-test in ./test/jdk/java/lang/reflect/callerCache/ReflectionCallerCacheTest.java
to create an instance directly from the class using newInstance() method which will reproduce the problem.

Currently there's a test only for calling a newInstance from the Constructor of a Class - AccessTest$PublicConstructor.


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
It would not leak the caller classloader

CUSTOMER SUBMITTED WORKAROUND :
None, its not possible to find all classes which were called by a classloader.

Unless you know especially which class you've called and all your external dependencies.


FREQUENCY : always



Comments
newInstanceCallerCache was used to cache the caller in older releases too when the quick member access path couldn't be used. So the leak has been there a long time for the less common cases, just not noticed.
03-07-2018