Hi Charles,
On 03/02/2018 09:22 PM, Charles Oliver Nutter wrote:
Put it another way: does a static reference from a class to itself
prevent that class from being garbage collected? Of course not.
ClassValue is intended to be a way to inject pseudo-static data into
either a class or a Class. Injecting that data, even if it has a
reference back to the class, should not prevent the class from being
collected.
And it should not with ClassValue either. The problem is when you
associate values with "system" classes (such as Object, String, etc.).
Such values become strongly reachable from Object.class, String.class,
etc., respectively. It's like injecting an instance field into Class
class and assigning Object.class.myField = myValue, String.class.myField
= myValue, ... or equivalently injecting static fields into Object,
String, etc class(es) and assigning Object.myField = myValue,
String.myField = myValue, ...
This would not in itself be a problem if myValue was composed only of
objects of system classes (like String, Integer, HashMap, ArrayList,
etc..), but you probably have your own Ruby runtime classes to maintain
the data structure behind myValue. And each of those custom objects
maintains an implicit reference to its runtime class, which is a Ruby
runtime class, loaded by whichever ClassLoader loaded the Ruby runtime,
so the chain of strong references extends from system class loader to
Ruby runtime class loader, effectively preventing it from being unloaded.
If you don't need to use ClassValue.remove() in your code, you could try
using the following wrapper above ClassValue. The overhead of its get()
method compared to ClassValue's should not be to big - just another
lookup into a ThreadLocal and a reference comparison with null (in the
general fast-path case):
/**
* {@link ClassValue} makes mapped values strongly reachable from
{@link Class}(es)
* they are associated with while they are not reachable directly from
{@link ClassValue}(s).
* This is some times desirable, but other times not. This wrapper
provides a
* ClassValue-like API where the roles are reversed - associated values
are not strongly
* reachable from Class(es) but from WeakClassValue(s) they are
associated with.
*
* @param <T> they type of associated values
*/
public abstract class WeakClassValue<T> {
// temporary holder of a Node computed in embedded
cv.computeValue() so we
// can later find out whether the embedded WeakReference has been
installed into
// ClassValueMap or not by comparing it to instance returned from
cv.get()
private static final ThreadLocal<Node> NODE_TL = new ThreadLocal<>();
// head of doubly-linked list of nodes, keeping associated values
reachable from
// WeakClassValue instance
private final Node head = new Node(null, null, null, null, null);
// a single reference queue for all Node(s) to get enqueued when
their referents
// (Class instances) get GCed...
private static final ReferenceQueue<Class<?>> queue = new
ReferenceQueue<>();
// Node is a PhantomReference tracking reachability of associated
Class.
// it also holds the associated value, WeakReference to it, and
prev/next pointers into the list
private static final class Node extends PhantomReference<Class<?>> {
private final Node head;
private Node prev, next;
private Object value;
WeakReference<?> ref;
Node(Class<?> type, ReferenceQueue<Class<?>> queue,
Node head, Object value, WeakReference<?> ref) {
super(type, queue);
this.value = value;
this.ref = ref;
if (head == null) {
// constructing head node
this.prev = this.next = this;
this.head = null;
} else {
// link node into list anchored by head
synchronized (head) {
this.prev = head;
this.next = head.next;
this.head = head; // remember where we belong to
head.next.prev = this;
head.next = this;
}
}
}
// unlink the node from the list using right head as a lock
void unlink() {
synchronized (head) {
prev.next = next;
next.prev = prev;
next = prev = null;
value = null;
ref.clear();
ref = null;
}
}
}
// work is delegated to embedded ClassValue instance
private final ClassValue<WeakReference<T>> cv = new ClassValue<>() {
@SuppressWarnings("unchecked")
@Override
protected WeakReference<T> computeValue(Class<?> type) {
T value = WeakClassValue.this.computeValue(type);
WeakReference<T> ref = new WeakReference<>(value);
// link the value into a doubly-linked list rooted at
// WeakClassValue instance so it is kept alive
Node node = new Node(type, queue, head, value, ref);
// assign the node temporarily to NODE_TL thread-local so
we can
// check later if embedded WeakReference got installed into
// ClassValueMap or not (depends on concurrent activity)
NODE_TL.set(node);
return ref;
}
};
/**
* Computes the given class's derived value for this {@code
WeakClassValue}.
*/
protected abstract T computeValue(Class<?> type);
/**
* Returns the value for the given class.
* If no value has yet been computed, it is obtained by
* an invocation of the {@link #computeValue computeValue} method.
*/
public T get(Class<?> type) {
WeakReference<T> ref = cv.get(type);
Node node = NODE_TL.get();
if (node != null) {
if (ref != node.ref) {
// some other thread has beaten us installing its
WeakReference
// into type's ClassValueMap. We must unlink our Node
from list
// to release our stale value
node.unlink();
}
NODE_TL.set(null);
}
try {
return ref.get();
} finally {
// keep type and this reachable as those are guarantees that
// the value is reachable too before it is get() from 'ref'
Reference.reachabilityFence(type);
Reference.reachabilityFence(this);
}
}
/**
* Removes any stale nodes from doubly-linked list(s) holding
values that
* were associated with classes that have been GCed. You cal call this
* explicitly to expedite removal of stale data if new values are not
* computed for some time
*/
public static void removeStaleValues() {
Node n;
while ((n = (Node) queue.poll()) != null) {
n.unlink();
}
}
}
You may freely use this code in Ruby or anywhere else if you find fit.
Regards, Peter
On Fri, Mar 2, 2018 at 2:19 PM Charles Oliver Nutter
<head...@headius.com <mailto:head...@headius.com>> wrote:
I have posted a modified version of my description to the main bug
report.
TLDR: ClassValue should not root objects.
- Charlie
On Fri, Mar 2, 2018 at 2:13 PM Charles Oliver Nutter
<head...@headius.com <mailto:head...@headius.com>> wrote:
Yes, it may be the same bug.
In my case, the ClassValue is held by a utility object used
for our Java integration. That utility object has to live
somewhere, so it's held by the JRuby runtime instance. There's
a strong reference chain leading to the ClassValue.
The value is a Ruby representation of the class, with
reflected methods parsed out and turned into Ruby endpoints.
Obviously, the value also references the class, either
directly or indirectly through reflected members.
The Ruby class wrapper is only hard referenced directly if
there's an instance of the object live and moving through
JRuby. It may be referenced indirectly through inline caches.
However...I do not believe this should prevent collection of
the class associated with the ClassValue.
The value referenced in the ClassValue should not constitute a
hard reference. If it is alive *only* because of its associate
with a given class, that should not be enough to root either
the object or the class.
ClassValue should work like ThreadLocal. If the Thread
associated with a value goes away, the value reference goes
away. ThreadLocal does nothing prevent it from being
collected. If the Class associated with a Value goes away, the
same should happen to that Value and it should be collectable
once all other hard references are gone.
Perhaps I've misunderstood?
- Charlie
On Fri, Mar 2, 2018 at 12:16 PM Vladimir Ivanov
<vladimir.x.iva...@oracle.com
<mailto:vladimir.x.iva...@oracle.com>> wrote:
Charlie,
Does it look similar to the following bugs?
https://bugs.openjdk.java.net/browse/JDK-8136353
https://bugs.openjdk.java.net/browse/JDK-8169425
If that's the same (and it seems so to me [1]), then speak
up and
persuade Paul it's an important edge case (as stated in
JDK-8169425).
Best regards,
Vladimir Ivanov
[1] new RubyClass(Ruby.this) in
public static class Ruby {
private ClassValue<RubyClass> cache = new
ClassValue<RubyClass>() {
protected RubyClass computeValue(Class<?> type) {
return new RubyClass(Ruby.this);
}
};
On 3/1/18 2:25 AM, Charles Oliver Nutter wrote:
> So I don't think we ever closed the loop here. Did
anyone on the JDK
> side confirm this, file an issue, or fix it?
>
> We still have ClassValue disabled in JRuby because of
the rooting issues
> described here and in
https://github.com/jruby/jruby/pull/3228.
>
> - Charlie
>
> On Thu, Aug 27, 2015 at 7:04 AM Jochen Theodorou
<blackd...@gmx.org <mailto:blackd...@gmx.org>
> <mailto:blackd...@gmx.org <mailto:blackd...@gmx.org>>>
wrote:
>
> One more thing...
>
> Remi, I tried your link with my simplified scenario
and it does there
> not stop the collection of the classloader
>
> Am 27.08.2015 11:54, schrieb Jochen Theodorou:
> > Hi,
> >
> > In trying to reproduce the problem outside of
Groovy I stumbled
> over a
> > case case which I think should work
> >
> > public class MyClassValue extends ClassValue {
> > protected Object computeValue(Class type) {
> > Dummy ret = new Dummy();
> > Dummy.l.add (this);
> > return ret;
> > }
> > }
> >
> > class Dummy {
> > static final ArrayList l = new ArrayList();
> > }
> >
> > basically this means there will be a hard
reference on the ClassValue
> > somewhere. It can be in a static or non-static
field, direct or
> > indirect. But this won't collect. If I put for
example a
> WeakReference
> > in between it works again.
> >
> > Finally I also tested to put the hard reference
in a third class
> > instead, to avoid this self reference. But it can
still not collect.
> >
> > So I currently have the impression that if
anything holds a hard
> > reference on the class value that the classloader
cannot be collected
> > anymore.
> >
> > Unless I misunderstand something here I see that
as a bug
> >
> > bye blackdrag
> >
>
>
> --
> Jochen "blackdrag" Theodorou
> blog: http://blackdragsview.blogspot.com/
>
> _______________________________________________
> mlvm-dev mailing list
> mlvm-dev@openjdk.java.net
<mailto:mlvm-dev@openjdk.java.net>
<mailto:mlvm-dev@openjdk.java.net
<mailto:mlvm-dev@openjdk.java.net>>
> http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev
>
> --
>
> - Charlie (mobile)
>
>
>
> _______________________________________________
> mlvm-dev mailing list
> mlvm-dev@openjdk.java.net <mailto:mlvm-dev@openjdk.java.net>
> http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev
>
--
- Charlie (mobile)
--
- Charlie (mobile)
--
- Charlie (mobile)
_______________________________________________
mlvm-dev mailing list
mlvm-dev@openjdk.java.net
http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev
_______________________________________________
mlvm-dev mailing list
mlvm-dev@openjdk.java.net
http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev