我运行一个Groovy / Grails系统,动态编译并加载用户定义的代码。这基本上是通过GroovyClassLoader
完成的。
我看到动态类本身都加载和卸载就好了。我添加了
-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled
标志,并且工作正常。当类加载器是GC时,动态类正在被卸载。
但是我的堆内存泄漏了,这就是泄漏的来源:
现在,Launcher$AppClassLoader
在parallelLockMap
中定义了一个属性java.lang.ClassLoader
。显然,这个ConcurrentHashMap
用于在Java 7中保存并行类加载锁。查看source code。
此地图中保留的条目包含String
个键(锁定适用的类名)和Object
值(锁定对象)。有两种泄漏密钥:
"groovy.runtime.metaclass.MyDynamicallyLoadedClassNameMetaClass"
"MyDynamicallyLoadedClassNameBeanInfo"
所以对我来说,当加载parallelLockMap
和*MetaClass
类时,看起来这些键是在*BeanInfo
中创建的,但是当关联的动态加载类(及其类)时它们不会被删除元类)卸载。现在,这已经深入到Groovy及其元类系统的内部,并且我已经没有专业知识了。
这最终会让你离开堆空间,虽然需要一段时间:
String newClass = "class CLASSNAME {}"
while (true) {
GroovyClassLoader gcl = new GroovyClassLoader()
Class clazz = gcl.parseClass(newClass.replace("CLASSNAME", "NewClass"+System.nanoTime()))
clazz.newInstance()
}
请务必使用上述JVM标志运行此操作,以便用完堆空间,而不是 PermGen 空间。再次,PermGen很好地收集垃圾,没有泄漏。
1)这是Groovy或Java 7中的错误吗?清除parallelLockMap
是谁的责任?我应该提交问题报告吗?
2)有解决方法吗?我正在考虑使用自定义ClassLoader
,它不首先尝试将类加载委托给这些MetaClass
和BeanInfo
类的父级,从而阻止调用java.lang.ClassLoader#loadClass(..)
。不过,我不是Java / Groovy类加载方面的专家。
编辑:在JDK方面,最近报告了此问题here。