Questions about JDK-8136353 "ClassValue preventing class unloading"

Peter Levart peter.levart at gmail.com
Fri Jan 8 07:26:57 UTC 2016



On 01/07/2016 03:05 PM, Jochen Theodorou wrote:
> Hi Peter,
>
> Am 06.01.2016 um 21:34 schrieb Peter Levart:
> [...]
>> Currently the cached instance of type T is reachable from Class<?>
>> instance and you are using Integer.TYPE in your example. Which means
>> that the cached instance of T is never going to be released if
>> ClassValue<T> instance is reachable from cached instance of T. In your
>> example, it is: (Dummy instance -> Dummy.class -> MyClassValue
>> instance). So you get this reachability chain: Integer.TYPE ->
>> ClassValueMap instance -> ClassValueMap.Entry[] -> ClassValueMap.Entry
>> -> Dummy instance -> Dummy.class -> MyClassValue instance.
> >
>> Integer.TYPE is a Class<?> instance representing int type which is
>> loaded by bootstrap class loader, which never goes away.
>
> So in other words, this is supposed to work if I used for example 
> Dummy instead of Integer.TYPE? Because this is not the case (just 
> tested with 1.8.0_66). Which means we are not talking only about 
> bootstrap classes, but about basically any class.

Hi Jochen,

I suspect you specified MyClassValue.Dummy.class instead of Integer.TYPE 
? Was Dummy.class loaded by same class loader as the MyClassValue for 
which the ClassValue.get() was executed. I have tried to execute the 
following variation of the test (no need to pack a separate jar file - 
you can execute if directly from within an IDE):

import java.io.File;
import java.io.UncheckedIOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Paths;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CVTest {

     public static class MyClassValue extends ClassValue {
         protected Object computeValue(Class type) {
             Dummy ret = new Dummy();
             Dummy.o = this;
             return ret;
         }
     }

     public static class Dummy {
         static Object o;

         @Override
         protected void finalize() throws Throwable {
             System.out.println(this + " finalized");
         }
     }

     public static void main(String[] args) throws Exception {
         String[] cp = System.getProperty("java.class.path")
.split(Pattern.quote(File.pathSeparator));
         URL[] urls = Stream.of(cp).map(f -> {
             try {
                 return Paths.get(f).toUri().toURL();
             } catch (MalformedURLException e) {
                 throw new UncheckedIOException(e);
             }
         }).collect(Collectors.toList()).toArray(new URL[0]);

         for (long i = 0; i < 10; i++) {
             URLClassLoader classLoader = new URLClassLoader(
                 urls, ClassLoader.getSystemClassLoader().getParent());
             ClassValue<?> cv = (ClassValue<?>)
classLoader.loadClass("CVTest$MyClassValue").newInstance();
             Class<?> key = classLoader.loadClass("CVTest$Dummy");
             Object value = cv.get(key);
             assert value.getClass().getClassLoader() == classLoader;
             assert key.getClassLoader() == classLoader;
             System.out.println(i);
             classLoader.close();
             classLoader = null;
             cv = null;
             key = null;
             value = null;
             System.gc();
             Thread.sleep(200L);
         }
     }
}


...and it prints:

0
CVTest$Dummy at 6b027549 finalized
1
CVTest$Dummy at 65c59659 finalized
2
CVTest$Dummy at 61fa1e39 finalized
3
CVTest$Dummy at 24f89c62 finalized
4
CVTest$Dummy at 5ca80d89 finalized
5
CVTest$Dummy at 15963f1b finalized
6
CVTest$Dummy at 242cf046 finalized
7
CVTest$Dummy at 44d7632d finalized
8
CVTest$Dummy at 588ffa7b finalized
9
CVTest$Dummy at 7cc57896 finalized


...indicating that objects do get released in this case.


Regards, Peter

>
>> ClassValue API could use a different strategy and reference the cached
>> instance of type T from ClassValue<T> instance instead, but then you
>> would have a problem with ClassValue instances reachable from other
>> class loaders than the cached instances of type T.
>
> yes, that is what I tried as well... I used my own map directly in 
> ClassValue and avoided the map ClassValue uses natively.
>
> So the solution for this case is basically using a Soft- or 
> Weak-Reference in Dummy, right?
>
> bye Jochen
> _______________________________________________
> mlvm-dev mailing list
> mlvm-dev at openjdk.java.net
> http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev



More information about the mlvm-dev mailing list