I've been battling some memory leaks, and I'm currently baffled by this issue. There's a web application classloader that was supposed to be garbage collected, but it isn't (even after I fixed several leaks). I dumped the heap with jmap and browsed it with jhat, found the classloader and checked the rootset references.
If I exclude weak refs, the list is empty! How is that possible, since an object held only by weak references should get garbage collected? (I performed GC many times in jconsole)
If I include weak refs, I get a list of references, all of which come from one of the following fields:
I couldn't find any reason why any of those references should prevent garbage collecting the classloader. Is it a gc bug? Special undocumented case? jmap/jhat bug? Or what?
And the weirdest thing... after sitting idle and gc-ing from time to time for about 40 min, without changing anything, it finally decided to unload classes and collect the classloader.
Note:
If you make a claim about delayed collection of classloaders or weak references, then please specify the circumstances in which it happens, and ideally:
If you think the behavior is implementation-dependent, then please focus on what happens in the oracle or icedtea jvm, version 6 or 7 (pick any one of them and be specific).
I'd really like to get to the bottom of this. I actually put some effort into reproducing the issue in a test program, and I failed - the classloader was instantly collected on System.gc() every time unless there was a strong reference to it.
All classes hold a reference to their classloader and all objects hold references to their classes. As a result, if an application gets unloaded but one of its objects is still being held (e.g., by a cache or a thread-local variable), the underlying classloader cannot not be removed by the garbage collector!
Consequently, there are two general forms of classloader leak: A classloader will be removed by the garbage collector only if nothing else refers to it. All classes hold a reference to their classloader and all objects hold references to their classes.
The application is said to have a strong reference to the object. A weak reference permits the garbage collector to collect the object while still allowing the application to access the object. A weak reference is valid only during the indeterminate amount of time until the object is collected when no strong references exist.
While Java classloader issues lead to the same runtime issues as the aforementioned dll-hell (multiple versions of the same class/method), they also lead to memory leaks and shortages that need to be addressed in any book about Java performance. When there are memory problems, one thinks primarily of normal objects.
It looks like there's a soft reference involved somewhere. That's the only explanation I could find for the delayed collection (about 40 min). I initially thought soft references were kept until the memory runs out, but I found that that's not the case.
From this page: "softly reachable objects will remain alive for some amount of time after the last time they were referenced. The default value is one second of lifetime per free megabyte in the heap. This value can be adjusted using the -XX:SoftRefLRUPolicyMSPerMB flag"
So I adjusted that flag to 1, and the classloader was collected within seconds!!
I think the soft reference comes from ObjectStreamClass. The question is why jhat doesn't show it in the rootset references. Is it because it's neither strong nor weak? Or because it already found weak references from the same static fields? Or some other reason? Either way, I think this needs to be improved in jhat.
Classes reside in special memory space - permanent generation. To unload classloader. GC should choose to include perm space into scope of collection. Different GC algorithms have a little different behavior, but generally GC will try to avoid perm space collection.
In my experience, even if classloader is not reachable JVM may end up with OutOfMemoryError before it would try to collect PERM space.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With