Guice 3.0 + Tomcat 7.0 = утечка памяти ClassLoader

Я знаю, что эта проблема была вокруг не менее 3-х лет (Issue 92), но я ' m все еще не удовлетворен текущим состоянием. Я также знаю, что это не влияет на Tomcat, если вы перезагружаетесь после повторного развертывания (как показано в потенциальной утечке памяти Guice + Tomcat).

Моя проблема в том, что я испытываю ошибки OutOfMemoryError: PermGen после некоторых перераспределений. Обратите внимание, что я не использую сборники google явно, я использую только Guice 3.0 (через maven). После анализа дампов кучи я все еще вижу, что поток com.google.inject.internal.Finalizer по-прежнему активен, сохраняет ссылку на Tomcat WebappClassLoader, тем самым препятствуя сбору мусора.

Что делать, если я действительно требует повторных развертываний без перезагрузки и использую Guice? Каковы мои варианты?

Ответ 1

Ну, никто не был там, чтобы помочь мне, поэтому вот что я узнал:

Нить Finalizer запускается FinalizableReferenceQueue (FRQ). Существует жесткая (статическая) ссылка на FRQ в MapMaker. WebAppClassLoader не собирался сбор мусора, потому что MapMaper все еще был вокруг из-за жесткой ссылки.

Следующий код решил мою проблему:

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);

Здесь код нарушения (com.google.inject.internal.util.MapMaker):

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

После этого поток Finalizer изящно умирает.