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





_______________________________________________
mlvm-dev mailing list
mlvm-dev@openjdk.java.net
http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev

Reply via email to