Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Guice 3.0 + Tomcat 7.0 = ClassLoader memory leak

I know that this problem has been around for at least 3 yeears (Issue 92), but I'm still not satisfied with the current state of it. I am also aware that this does not affect Tomcat if you do restart after redeploying (as suggested in Guice + Tomcat potential memory leak).

My problem is that I am experiencing OutOfMemoryError: PermGen errors after some redeployments. Notice that I am not using google-collections explicitly, I am only using Guice 3.0 (via maven). After analyzing heap dumps, I still see that the thread com.google.inject.internal.Finalizer is still active, keeps a reference to Tomcat's WebappClassLoader, thus hindering garbage collection.

What if I actually require redeployments without restarting and am using Guice? What are my options?

like image 322
chahuistle Avatar asked Jan 12 '12 21:01

chahuistle


1 Answers

Well, no one was there to help me, so here's what I learned:

The Finalizer thread is started by the FinalizableReferenceQueue (FRQ). There is a hard (static) reference to the FRQ in MapMaker. The WebAppClassLoader was not garbage collected because MapMaper was still around due to the hard reference.

The following code solved my problem:

final Class<?> queueHolderClass = 
    Class.forName("com.google.inject.internal.util.$MapMaker$QueueHolder");
final Field queueField = queueHolderClass.getDeclaredField("queue");
// make MapMaker.QueueHolder.queue accessible
queueField.setAccessible(true);
// remove the final modifier from MapMaker.QueueHolder.queue
final Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(queueField, queueField.getModifiers() & ~Modifier.FINAL);
// set it to null
queueField.set(null, null);

Here's the offending code (com.google.inject.internal.util.MapMaker):

/** Wrapper class ensures that queue isn't created until it's used. */
private static class QueueHolder {
  static final FinalizableReferenceQueue queue = new FinalizableReferenceQueue();
}

After doing this, the Finalizer thread gracefully dies.

like image 94
chahuistle Avatar answered Oct 30 '22 10:10

chahuistle