GroovyCategorySupport 和 "system" 内存泄漏
GroovyCategorySupport and "system" memory leak
当我运行下面的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 中的错误有关,我正在寻找缓解此问题的方法:
- 我的测试可能有误?如何在不泄漏堆 space 或 permgen space 的情况下泄漏 "system" 内存?
- 它是 "known" 错误还是 Groovy 和 JRE 1.7 之间不兼容?
- 如何在 1.7 jre 中使用 groovy 类别而不遭受此内存泄漏?
编辑
我可以通过调用 VMPluginFactory.getPlugin().invalidateCallSites()
来重现这个错误,它用这个 "pure 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()
就够了
是的,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. Unfortunately, it has been fixed only in JDK 9. By a lucky chance your problem is not seen on JDK 8 because of MethodHandles refactoring done in JDK-8050166。尽管 MemberNameTable 探测器仍然存在,但 SwitchPoint()
不再创建新的 MemberNames。后一个修复也被反向移植到 JDK 7u91.
Groovy 运行时如果检测到 Java 7+,则使用 MethodHandles。您可以通过修补 VMPluginFactory
to use Java 6 plugin. Here is the patch 来解决此问题。如果包含在 Groovy 库之前的类路径中,它将强制 Groovy 运行时使用 Java 6 - 兼容的 VMPlugin。
因此,您有以下选项来解决内存泄漏问题:
- 使用 JRE 8(推荐)
- 使用 JRE 7u91+
- 在类路径中包含 VMPluginFactory 补丁
当我运行下面的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 中的错误有关,我正在寻找缓解此问题的方法:
- 我的测试可能有误?如何在不泄漏堆 space 或 permgen space 的情况下泄漏 "system" 内存?
- 它是 "known" 错误还是 Groovy 和 JRE 1.7 之间不兼容?
- 如何在 1.7 jre 中使用 groovy 类别而不遭受此内存泄漏?
编辑
我可以通过调用 VMPluginFactory.getPlugin().invalidateCallSites()
来重现这个错误,它用这个 "pure 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()
就够了
是的,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. Unfortunately, it has been fixed only in JDK 9. By a lucky chance your problem is not seen on JDK 8 because of MethodHandles refactoring done in JDK-8050166。尽管 MemberNameTable 探测器仍然存在,但 SwitchPoint()
不再创建新的 MemberNames。后一个修复也被反向移植到 JDK 7u91.
Groovy 运行时如果检测到 Java 7+,则使用 MethodHandles。您可以通过修补 VMPluginFactory
to use Java 6 plugin. Here is the patch 来解决此问题。如果包含在 Groovy 库之前的类路径中,它将强制 Groovy 运行时使用 Java 6 - 兼容的 VMPlugin。
因此,您有以下选项来解决内存泄漏问题:
- 使用 JRE 8(推荐)
- 使用 JRE 7u91+
- 在类路径中包含 VMPluginFactory 补丁