Question in understanding ClassValue better

64 views
Skip to first unread message

Jochen Theodorou

unread,
May 19, 2016, 10:27:12 AM5/19/16
to jvm-la...@googlegroups.com, Technical discussion about the implementation of the Multi-Language Virtual Machine
Hi,

at the beginning of this year I had an exchange with Peter Lavart about
JDK-8136353 (see
http://mail.openjdk.java.net/pipermail/mlvm-dev/2016-January/006563.html),
and that is probably based on wrong assumptions. But I must confess I
still have trouble understanding ClassValue semantics. And since the
ClassValue problem in Groovy came up again, I though I make another try
based on a list of assumptions and asking if they are wrong or right

1) ClassValue can be basically understood as a strong reference of a
class to a class value
2) a ClassValue associated with a system class (for example
Integer.TYPE) is never garbage collected
3) a ClassValue from a different loader than the system loader,
associated with a system class, will prevent that loader to unload
4) a ClassValue referencing to the class it is associated with, does not
prevent the collection of that class

Point 2 and 3 are kind of problematic for me and I wish them wrong, but
they would follow from 1. The exchange with Peter makes me think
assumption 4 is wrong... just I don't understand why.

If those assumptions are right, then I actually wonder in what cases I
should use ClassValue without causing memory leaks. What I wanted to use
it for is to associate a meta class with every class I need a meta class
for. This includes system classes. If 3 is right, then doing so would
prevent the Groovy runtime from being unloaded. Even if the meta classes
are able to unload, the implementation of the ClassValue would still be
there. And since that comes from the same loader, that loaded the
runtime, that loader will stay. Now loading and (trying to) unload the
Groovy runtime countless times would end up in a OOME at some point
(permgen problem in older JDKs). And even if I would do something else
for class from the standard loaders, I would still get into trouble on
for example Tomcat. Not to forget that having two parallel structures
for this raises the question as of why to use ClassValue at all.

I think what it boils down to in the end is: When (under what
conditions) for what to use ClassValue at all.

bye Jochen

Jochen Theodorou

unread,
May 19, 2016, 7:33:07 PM5/19/16
to Peter Levart, Technical discussion about the implementation of the Multi-Language Virtual Machine, jvm-la...@googlegroups.com
On 19.05.2016 21:32, Peter Levart wrote:
[...]
> a ClassValue instance can be thought of as a component of a compound
> key. Together with a Class, they form a tuple (aClass, aClassValue) that
> can be associated with an "associated value", AV. And yes, the AVs
> associated with tuple containing a particular Class are strongly
> reachable from that Class.

I see, I was mixing ClassValue and AV, I was more talking about AV, than
ClassValue itself, though ClassValue surely plays an important role in
here. Anyway, I am going for that AV is the value computed by aClassValue.

You said that the AV is strongly reachable from aClass. Can I further
assume, that aClassValue is not strongly reachable from aClass? And that
aClassValue can be collected independent of aClass? Can I further
assume, that aClassValue can be collected even if AVs for it continue to
exist?

[...]
> An AV associated with a tuple (Integer.TYPE, aClassValue) -> AV can be
> garbage collected. But only if aClassValue can be garbage collected 1st.

hmm... so in (aClass, aClassValue)->AV if aClassValue can be collected,
AV can, but not the other way around... what about aClass? if nothing
but AV is referencing aClass, can AV be garbage collected, even if
aClassValue cannot? Can I extend your statement to AV can be collected
only if either aClass or aClassValue can be garbage collected first?

Let us assume this is the case for now.

> This is the most tricky part to get right in order to prevent leaks. If
> in above example, aClassValue is reachable from the AV, then we have a
> leak. The reachability of a ClassValue instance from the associated
> value AV is not always obvious. One has to take into account the
> following non-obvious references:
>
> 1 - each object instance has an implicit reference to its implementing class
> 2 - each class has a reference to its defining ClassLoader
> 3 - each ClassLoader has a reference to all classes defined by it
> (except VM annonymous classes)
> 4 - each ClassLoader has a reference to all its predecessors (that it
> delegates to)
>
> Since a ClassValue instance is typically assigned to a static final
> field, such instance is reachable from the class that declares the field.
>
> I think you can get the picture from that.

yeah... that is actually problematic. Because if I keep no hard
reference the ClassValue can be collected, even if the AVs still
exist... meaning they would become unreachable. And if I keep one I have
a memory leak... well more about in the program you have shown me later on.

[...]
> Ok, let's set up the stage. If I understand you correctly, then:
>
> Groovy runtime is loaded by whatever class loader is loading the
> application (see the comment in MetaClass constructor if this is not
> true). This is either the ClassLoader.getSystemClassLoader() (the APP
> class loader) if started from command line or for example Web App class
> loader in a Web container.

Well, actually... if you start a script on the command line, the loader
is a child to the app class loader, when used as library it could be the
app loader (for example if the groovy program is precompiled) and in a
tomcat like scenario it could be either the class loader for the web
app, or the loader for all web apps. But let's go with the cases you
mentioned first ;)

> MetaClass(es) are objects implemented by Groovy runtime class(es). Let's
> call them simply MetaClass.

good

> Here's how I would do that:
>
>
> public class MetaClass {
>
> // this list keeps MetaClass instances strongly reachable from the MetaClass
> // class(loader) since they are only weakly reachable from their associated
> // Class(es)
> private static final ArrayList<MetaClass> META_CLASS_LIST = new ArrayList<>();
>
> // this WeakReference is constructed so that it keeps a strong reference
> // to a referent until releaseStrong() is called
> private static final class WeakEntry extends WeakReference<MetaClass> {
> private final AtomicReference<MetaClass> strong;
>
> WeakEntry(MetaClass mc) {
> super(mc);
> strong = new AtomicReference<>(mc);
> }
>
> boolean releaseStrong() {
> MetaClass mc = strong.get();
> return mc != null && strong.compareAndSet(mc, null);
> }
> }
>
> private static final ClassValue<WeakEntry> WEAK_ENTRY_CV =
> new ClassValue<WeakEntry>() {
> @Override
> protected WeakEntry computeValue(Class<?> type) {
> return new WeakEntry(new MetaClass(type));
> }
> };
>
> // the public API
> public MetaClass getInstanceFor(Class<?> type) {
> WeakEntry entry = WEAK_ENTRY_CV.get(type);
> MetaClass mc = entry.get();
> if (entry.releaseStrong()) {
> synchronized (META_CLASS_LIST) {
> META_CLASS_LIST.add(mc);
> }
> }
> return mc;
> }
>
> MetaClass(Class<?> type) {
> // derive it from 'type', but don't reference it
> // strongly if Groovy runtime is loaded by a parent
> // class loader of the application class loader
> // that loads application classes you want
> // MetaClass(es) to be derived from.
> }
> }
>
>
> This will keep MetaClass instances isolated from the associated classes
> but still keep them reachable as long as the
> MetaClass.class.getClassLoader() is alive.
>
> Is this going to help?

partially... MetaClass will realistically have a strong reference to the
class the meta class is for and a lot of other classes. I will need to
reference methods and fields by reflection and at some point keep them
referenced. Through the declaring class I should then again have a
strong reference to the class.

1_000_000.times { Eval.me("42") }

this will create 1 million classes, in 1 million classloaders, which are
a child to the class loader for the Groovy runtime. Each of them will
get a meta class... ignoring the list, if the strong reference to the
script class in MetaClass is here enough to prevent collection, then
this is a problem. The list of course is also a problem, since keeping
them alive as long as MetaClass.class.getClassLoader is alive, is
actually not the semantics I need.

The semantics I need is something like lifetime(AV) is roughly
min(lifetime(aClass),lifetime(aClassValue)), with AV referencing aClass
not influencing it being collectable, as long as this association is the
only reference to AV. If it is the max of them I am in trouble.

And seeing how aClass kind of has a strong reference to AV and that if
aClassValue and AV are from the same loader and if AV has a strong
reference to aClass as well, it will cause aClass not being collectable
for as long as the loader for aClassValue and AV exists... because
aClassValue is a static value in one of the classes, and AV is not
collected before aClassValue is collected and aClass is not not
collected because of the reference in AV.

The "old" solution in Groovy is to keep a "global" map with weak class
keys and soft references to MetaClass. This allows in theory the class
being collectable, but only if the MetaClass has been collected before..
and some VMs really do not like soft referenced classes much (forced us
to use the mark and sweep gc for example). Also soft references used not
always to be clean up when permgen space is low. But if they are not
cleaned up, then MetaClass prevents the garbage collection of the class,
causing permgen problems. Not sure how that is with meta space, but from
what I have seen so far, this can still happen. And of course using
SoftReference means we garbage collect relatively late, and cause a big
garbage collection. I cannot use weak references, because tend to be
collected to fast and creating the meta class is not exactly a cheap
operation.

bye Jochen

Jochen Theodorou

unread,
May 24, 2016, 4:26:34 AM5/24/16
to Peter Levart, Technical discussion about the implementation of the Multi-Language Virtual Machine, jvm-la...@googlegroups.com
Peter, I fully understand if you cannot reply to this mail easily, just
wanted to ping to ensure this is not forgotten ;)
> _______________________________________________
> mlvm-dev mailing list
> mlvm...@openjdk.java.net
> http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev

Reply all
Reply to author
Forward
0 new messages