Question in understanding ClassValue better

Jochen Theodorou blackdrag at gmx.org
Tue May 24 15:19:38 UTC 2016


On 24.05.2016 15:33, Peter Levart wrote:
> On 05/24/2016 01:41 PM, Peter Levart wrote:
>> Hm,
>>
>> It seems that my example will not help much. It also seems that the
>> only problem with plain:
>>
>>     static final ClassValue<MetaClass> META_CLASS_CV = new
>> ClassValue<MetaClass>() {
>>         @Override
>>          protected MetaClass computeValue(Class<?> type) {
>>              return new MetaClass(type);
>>          }
>>     };
>>
>> ...is the fact that MetaClass is a class loaded by non-bootstrap class
>> loader and that in case this is a Web app class loader, it prevents
>> undeployment. Can you confirm that a MetaClass instance only
>> references the 'type' Class<?> it is derived from (it's Methods,
>> Fields, etc.) and never references objects from any child class
>> loaders of the type's class loader?

If I have the meta class for Integer, then the metaclass itself is an 
object from a child loader of the loader of Integer. Which means "no" 
unless I understand the question wrongly.

>> If that is the case, then you could replace MetaClass with a generic
>> data structure, composed of instances of bootstrap classes (HashMap,
>> ArrayList, Object[], ...). That way, Groovy runtime class loader will
>> not be "captured" by a reference from an aClass loaded by bootstrap
>> class loader.
>>
>> Is MetaClass a complicated data structure?
>
> ...peeking at Groovy sources, very much so.

yes... probably more complex than needed.

> There's a solution though.
> Various Meta* classes in Groovy runtime reference at some point the
> reflective objects (Class, Method, Constructor, Field) describing the
> 'type' they are derived from.
>
> Every reference to a Class object from such Meta* object should be
> wrapped in something like the following:
>
> public final class ClassReference extends WeakReference<Class<?>>
>      implements Supplier<Class<?>> {
>
>      private static final ConcurrentHashMap<ClassReference,
> ClassReference> MAP
>          = new ConcurrentHashMap<>();
>      private static final ReferenceQueue<Class<?>> QUEUE
>          = new ReferenceQueue<>();
>
>      public static ClassReference forClass(Class<?> clazz) {
>          ClassReference oldRef;
>          while ((oldRef = (ClassReference) QUEUE.poll()) != null) {
>              MAP.remove(oldRef);
>          }
>          ClassReference newRef = new ClassReference(clazz);
>          oldRef = MAP.putIfAbsent(newRef, newRef);
>          return oldRef == null ? newRef : oldRef;
>      }
>
>      private final String name;
>      private final int hash;
>
>      private ClassReference(Class<?> clazz) {
>          super(clazz, QUEUE);
>          name = clazz.getName();
>          hash = clazz.hashCode();
>      }
>
>      @Override
>      public Class<?> get() {
>          Class<?> clazz = super.get();
>          if (clazz == null) {
>              throw new IllegalStateException(
>                  "Class " + name + " has already been unloaded");
>          }
>          return clazz;
>      }
>
>      @Override
>      public int hashCode() {
>          return hash;
>      }
>
>      @Override
>      public boolean equals(Object obj) {
>          Class<?> clazz;
>          return obj == this ||
>                 (obj instanceof ClassReference &&
>                  (clazz = get()) != null &&
>                  clazz == ((ClassReference) obj).get());
>      }
> }
>
> Every reference to a Method should be wrapped in something like this:
>
>
> public final class MethodReference implements Supplier<Method> {
>      private static final ClassValue<Method[]> DECLARED_METHODS_CV =
>          new ClassValue<Method[]>() {
>              @Override
>              protected Method[] computeValue(Class<?> type) {
>                  return type.getDeclaredMethods();
>              }
>          };
>
>      private final ClassReference declaringClassRef;
>      private final int index;
>
>      public MethodReference(Method method) {
>          Class<?> declaringClass = method.getDeclaringClass();
>          declaringClassRef = ClassReference.forClass(declaringClass);
>          Method[] methods = DECLARED_METHODS_CV.get(declaringClass);
>          index = Arrays.asList(methods).indexOf(method);
>      }
>
>      @Override
>      public Method get() {
>          return DECLARED_METHODS_CV.get(declaringClassRef.get())[index];
>      }
>
>      @Override
>      public int hashCode() {
>          return declaringClassRef.hashCode() * 31 + index;
>      }
>
>      @Override
>      public boolean equals(Object obj) {
>          return obj == this || (
>              obj instanceof MethodReference &&
>              ((MethodReference) obj).declaringClassRef ==
> this.declaringClassRef &&
>              ((MethodReference) obj).index == this.index
>          );
>      }
> }
>
> And similar with every reference to a Constructor or Field.
>
> In addition, the MetaClass structure should be isolated from the class
> it is derived from with what I presented in the previous message
> (ClassValue<WeakReference<MetaClass>> + ArrayList<MetaClass> referenced
> from MetaClass static field)
>
> Would that work?

So the rule of thumb would be to either use only bootstrap classes as AV 
(and values strongly reachable from it) if they reference aClass 
strongly. Or to store a WeakReference as AV, which then can have a value 
that makes aClass strongly reachable from there, since it would realize 
a weak reachability in total. It would mean a very big rewrite of a lot 
of complicated classes, but I can see the idea.

I think the idea of "ClassValue<WeakReference<MetaClass>> + 
ArrayList<MetaClass> referenced from MetaClass static field" part needs 
to be extended though. As soon as I notice a class is not reachable 
anymore, I need to destroy its metaclass as well. I am imagining here a 
Map<ClassReference,MetaClass> structure instead of the ArrayList. it 
could potentially replace the MAP in ClassReference as well.

Then I see how the meta class would exist for about as long as the 
class, and how it would not be collected before. Actual collection of 
the meta class would happen in a GC run after the GC run, that collected 
the class. So there is still potential for an OOME if no classes could 
be collected, but still meta classes to be freed. Mostly because I 
cannot check my reference queue and cause the removal of the meta class 
out of the strongly referencing structure on memory pressure.

But anyway... I think that could work.

bye Jochen







More information about the mlvm-dev mailing list