GroovyCategorySupport и "системная" утечка памяти

Когда я запускаю следующий тест JUnit, память процесса java постоянно увеличивается. Через несколько часов он использует более 2го. Тем не менее, когда я смотрю с jvisualvm, размер кучи и permgen стабильны, я не вижу утечки. Тест запускается с -Xmx32m

public class TestCat {  
  public static class A { }  

  @Test 
  public void testCategory() { 
    for(;;) { 
      GroovyCategorySupport.use(A.class, new Closure<Object>(null) { 
        public Object call() { return null; } 
      }); 
    } 
  } 
} 

Я протестировал его с Groovy 2.4.7, Windows и JRE1.7_80, MacOS и JRE1.7_60. Я не могу воспроизвести эту ошибку с MacOS и JRE 1.8.0_91

Я предполагаю, что это связано с ошибкой в ​​JRE1.7, и я ищу способ смягчить эту проблему:

  • мой тест может быть не так? Как можно утечь "системную" память без утечки пространства кучи или пространства permgen?
  • Это "известная" ошибка или несовместимость между Groovy и JRE 1.7?
  • Как использовать Groovy категории с 1,7 JRE и без страданий этой утечки памяти?

РЕДАКТИРОВАТЬ

Я могу воспроизвести эту ошибку, позвонив VMPluginFactory.getPlugin().invalidateCallSites(), что переводится с помощью этого "чистого Java" модульного теста:

public class TestSwitchPoint {

  @Test
  public void testSP() {
    SwitchPoint switchPoint = new SwitchPoint();
    for(;;) {
      SwitchPoint old = switchPoint;
      switchPoint = new SwitchPoint();
      SwitchPoint.invalidateAll(new SwitchPoint[]{old});
    }
  }
}

На самом деле, только new SwitchPoint() достаточно.

1 ответ

Решение

Да, в JRE есть ошибка. Собственная утечка памяти происходит внутри JVM в следующем месте:

(VM)
 - os::malloc(unsigned long, unsigned short, unsigned char*)
 - CHeapObj<(unsigned short)1792>::operator new(unsigned long, unsigned char*)
 - JNIHandleBlock::allocate_block(Thread*)
 - JNIHandleBlock::allocate_handle(oopDesc*)
 - JNIHandles::make_weak_global(Handle)
 - instanceKlass::add_member_name(int, Handle)
 - MethodHandles::init_method_MemberName(Handle, methodOopDesc*, bool, KlassHandle)
 - MethodHandles::init_method_MemberName(Handle, CallInfo&, Thread*)
 - MethodHandles::resolve_MemberName(Handle, KlassHandle, Thread*)
 - MHN_resolve_Mem
(JAVA)
 - java.lang.invoke.MethodHandleNatives.resolve(MemberName, Class)
 - java.lang.invoke.MemberName$Factory.resolve(byte, MemberName, Class)
 - java.lang.invoke.MemberName$Factory.resolveOrNull(byte, MemberName, Class)
 - java.lang.invoke.DirectMethodHandle.maybeRebind(Object)
 - java.lang.invoke.DirectMethodHandle.bindReceiver(Object)
 - java.lang.invoke.CallSite.makeDynamicInvoker()
 - java.lang.invoke.MutableCallSite.dynamicInvoker()
 - java.lang.invoke.SwitchPoint.<init>()
 - Test.main(java.lang.String[])

Это известная проблема с MemberNameTable: JDK-8152271. К сожалению, это было исправлено только в JDK 9. По счастливой случайности ваша проблема не видна в JDK 8 из-за рефакторинга MethodHandles, выполненного в JDK-8050166. Несмотря на то, что MemberNameTable остается, SwitchPoint() больше не создает новые имена участников. Последнее исправление также было перенесено в JDK 7u91.

Groovy runtime использует MethodHandles, если обнаруживает Java 7+. Вы можете обойти это путем исправления VMPluginFactory использовать плагин Java 6. Вот патч. Если он включен в classpath до библиотек Groovy, он заставит среду исполнения Groovy использовать Java 6 -совместимый VMPlugin.

Итак, у вас есть следующие варианты, чтобы обойти утечку памяти:

  • используйте JRE 8 (рекомендуется)
  • используйте JRE 7u91+
  • включить патч VMPluginFactory в classpath
Другие вопросы по тегам