【发布时间】:2016-09-15 00:41:45
【问题描述】:
我正在加载大量 Groovy (2.4.6) 脚本并在我的 Java 8 应用程序中使用 GroovyScriptEngineImpl 运行它们,但一段时间后我遇到了问题。
有几件事你需要知道:
- 每次运行脚本时我都必须重新创建一个新的
GroovyScriptEngineImpl - 每次运行脚本时我都必须重新创建一个新的
GroovyClassLoader
为了将每个脚本隔离在单独的“环境”中,我需要这样做:我在类加载器中为某些脚本加载了一些外部 JAR,我不希望其他脚本能够使用执行时这些 JAR 中的类。
我的问题在于,对于我运行的每个脚本,GroovyClassLoader 都会创建一个新的 ScriptXXXX 类并加载它,但永远不会卸载它。
这导致加载的类数量无限增加,最终内存被完全填满。
我已经尝试了大量的各种解决方案,但似乎都没有奏效:
- 在 JVM 参数中添加
-XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC - 在 JVM 参数中添加
-Dgroovy.use.classvalue=true - 删除为每个
ScriptXXXX类创建的“元类”,如下所示:Groovy Classes not being collected but not signs of memory leak - 清除缓存并关闭
GroovyClassLoader - 使用自省手动清除缓存
GroovyScriptEngineImpl中的类的一些字段 - 等等……
这是 Eclipse 内存分析器中 ScriptXXXX 类之一的“GC 最短路径”:
我显然在这里没有解决方案,而且似乎没有一个真正起作用,因为类加载器总是保留对永远不会被 GCed 的类的引用。
如果你想重现问题,这里有一个代码示例:
GroovyScriptEngineImpl se;
while (true)
{
se = new GroovyScriptEngineImpl(new GroovyClassLoader());
CompiledScript script = se.compile("println(\"hello\")");
script.eval(se.createBindings());
}
谢谢
更新:看了pczeus的回复后,我尝试限制元空间,有些类似乎确实在卸载,我认为是ScriptXXX类。
也就是说,几分钟后我在脚本执行期间收到Out of Metaspace 错误。
这是我使用 VisualVM 获得的配置文件:
Eclipse 内存分析器中 ScriptXXX 类的“GC 路径”确实是空的(它们不再是类的实例),即使该类仍在直方图中列出。
【问题讨论】:
-
“大量”脚本有多少个?
-
可能无限。实际上它们可能只是几个不同的脚本,但正如我所说,它们必须有一个隔离的环境,因此每次都重新编译它们。例如,我在 1 小时内运行了一个测试,该测试创建了大约 10,000 个脚本并使用了 1GB 的 RAM。即使脚本数量较少,应用程序也将是一个不应该经常重启的服务器,因此
ScriptXXX类的数量无论如何都会增加。 -
好吧,我帖子末尾的代码有我面临的确切问题:它在循环的每一轮创建一个
ScriptXXX类,无论我做什么,这些类不会被类加载器释放,也不会被 GCed,最终填满内存。关于隔离的部分本身并不重要,您只需要知道我必须在每次执行时重新编译脚本,并且我必须在每次执行时使用新的GroovyScriptEngineImpl和新的GroovyClassLoader。跨度> -
@HardikModha 不。我停止为我开发此软件的公司工作,所以我不知道他们是否最终修复了它,但我所做的是在我重用脚本时缓存脚本。所以基本上我为每个新脚本存储
GroovyClassLoader,然后重用它。它有问题(例如静态变量未重置......)但这是一种解决方法...... -
感谢您的更新。就我而言,我从代码中删除了所有
Eval.x并实现了我自己的方法,并且效果很好。
标签: java groovy memory-leaks classloader