1

当我运行以下 JUnit 测试时,java 进程的内存不断增加。几个小时后,它使用了 2go 以上。但是,当我查看 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 之间的“已知”错误或不兼容吗?
  • 如何使用 1.7 jre 的 groovy 类别而不会遭受这种内存泄漏?

编辑

我可以通过调用来重现这个错误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()够用。

4

1 回答 1

3

是的,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-8050166中完成了 MethodHandles 重构,您的问题在 JDK 8 上没有出现。尽管 MemberNameTable 探针仍然存在,SwitchPoint()但不再创建新的 MemberNames。后一个修复也向后移植到 JDK 7u91。

如果 Groovy 运行时检测到 Java 7+,它会使用 MethodHandles。您可以通过修补VMPluginFactory以使用 Java 6 插件来解决此问题。这是补丁。如果在 Groovy 库之前包含在类路径中,它将强制 Groovy 运行时使用与 Java 6 兼容的 VMPlugin。

因此,您有以下选项来解决内存泄漏问题:

  • 使用 JRE 8(推荐)
  • 使用 JRE 7u91+
  • 在类路径中包含 VMPluginFactory 补丁
于 2016-09-11T20:22:39.823 回答