8169425: Values computed by a ClassValue should not strongly reference the ClassValue

Peter Levart peter.levart at gmail.com
Wed Nov 9 12:31:12 UTC 2016


Hi Paul,

On 11/09/2016 09:13 AM, Peter Levart wrote:
>
> It is not always the case that when ClassValue instance is strongly 
> reachable from the associated computed value, unloading of classes and 
> class loaders is prevented.

Take the following example (assume that the code is loaded by a special 
- say container - app class loader):

public class MyApp {
     public static final ClassValue<Object> CV = new ClassValue<Object>() {
         @Override
         protected Object computeValue(Class<?> type) {
             return CV;
         }
     };

     public static void main(String[] args) {
         CV.get(MyApp.class); // this is OK, it only makes CV reachable 
from MyApp.class
         CV.get(Object.class); // WARNING!!! this will make CV reachable 
from Object.class, pinning it for the VM's lifetime
     }
}


So the advise could be: "Avoid referencing ClassValue instance from 
associated values, but if you really must, you can do that safely only 
if you limit associating values with classes that have been loaded by 
the same class loader as the ClassValue instance runtime class (a 
subclass of ClassValue) or any descendant class loader.

We see that what causes the problem in above situation is the fact that 
the CV instance's runtime class is a subclass of ClassValue and that it 
is loaded by a non-system class loader. The above situation could be 
prevented by a special concrete ClassValue implementation, provided by 
the platform (loaded by bootstrap CL):

public class WeakSupplierClassValue<T> extends ClassValue<T> {
     private final WeakReference<Supplier<T>> supplierRef;

     public WeakSupplierClassValue(Supplier<T> supplier) {
         supplierRef = new WeakReference<>(supplier);
     }

     @Override
     protected T computeValue(Class<?> type) {
         Supplier<T> supplier = supplierRef.get();
         if (supplier == null) {
             throw new IllegalStateException("Supplier has already been 
GCed");
         }
         return supplier.get();
     }
}


...with such utility class, one could rewrite above example to:

public class MyApp {
     // make CV_SUPPLIER stay at least until MyApp class is alive
     private static final Supplier<Object> CV_SUPPLIER = () -> MyApp.CV;

     public static final ClassValue<Object> CV =
         new WeakSupplierClassValue<>(CV_SUPPLIER);

     public static void main(String[] args) {
         // this is OK
         CV.get(MyApp.class);

         // even this is OK, it makes CV reachable from Object.class,
         // but CV_SUPPLIER is only weakly reachable
         CV.get(Object.class);
     }
}


Regards, Peter



More information about the core-libs-dev mailing list