Search code examples
javamemory-leaksgarbage-collectionclassloader

Classloader held by weak references?


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:

  • java.lang.reflect.Proxy.loaderToCache
  • java.lang.reflect.Proxy.proxyClasses
  • java.io.ObjectStreamClass$Caches.localDescs
  • java.io.ObjectStreamClass$Caches.reflectors
  • java.lang.ref.Finalizer.unfinalized

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:

  • provide a link to an authoritative article that supports your claim
  • provide a sample program that demonstrates the behavior

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.


Solution

  • 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.