Ant*_*neB 9 java groovy memory-leaks classloader
我正在加载大量的Groovy(2.4.6)脚本并在我的Java 8应用程序中使用GroovyScriptEngineImpl运行它们,一段时间后我遇到了问题.
您需要了解一些事项:
GroovyScriptEngineImpl
每次运行脚本时我都必须重新创建一个新的GroovyClassLoader
每次运行脚本时我都必须重新创建一个新的我需要这样做,以便在一个单独的"环境"中隔离每个脚本:我在类加载器中为一些脚本加载一些外部JAR,我不希望其他脚本能够使用这些脚本中的类JAR在被执行时.
我的问题来自这样一个事实:对于我运行的每个脚本,GroovyClassLoader
它将创建一个新ScriptXXXX
类并加载它,但永远不会卸载它.
这导致加载的类数量无限增加,并且内存最终被完全填充.
我尝试了大量的各种解决方案,但似乎都没有效果:
-XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC
JVM参数-Dgroovy.use.classvalue=true
JVM参数ScriptXXXX
类创建的"元类" ,如下所示:Groovy类没有被收集但没有内存泄漏的迹象GroovyClassLoader
GroovyScriptEngineImpl
这是ScriptXXXX
Eclipse Memory Analyzer中其中一个类的"最短路径到GC" :
我在这里显然没有解决方案,似乎没有一个真正起作用,因为类加载器总是保持对从未获得GCed的类的引用.
如果您想重现该问题,请参阅以下代码示例:
GroovyScriptEngineImpl se;
while (true)
{
se = new GroovyScriptEngineImpl(new GroovyClassLoader());
CompiledScript script = se.compile("println(\"hello\")");
script.eval(se.createBindings());
}
Run Code Online (Sandbox Code Playgroud)
谢谢
更新:在阅读了pczeus的回复之后,我尝试限制元空间,并且有些类似乎确实正在卸载,我认为这是ScriptXXX
类.
也就是说,几分钟后我Out of Metaspace
在脚本执行过程中遇到错误.
这是我用VisualVM获得的配置文件:
Eclipse Memory Analyzer中用于ScriptXXX
类的"GC路径" 确实是空的(它们不再是类的实例),即使该类仍然在直方图中列出.
这显然是一种边缘情况,需要特殊调整,并可能选择所使用的垃圾收集方案。
由于您使用的是 Java8,并且您知道正在加载数千个临时类,因此您应该尝试调整和限制可用数量MetaSpace
并调整清理频率。直接来自https://blogs.oracle.com/poonam/entry/about_g1_garbage_collector_permanent:
JDK8:元空间
在 JDK 8 中,类元数据现在存储在本机堆中,该空间称为元空间。JDK 8 中为元空间添加了一些新标志:
-XX:MetaspaceSize= 其中是为类元数据分配的初始空间量(初始高水位线)(以字节为单位),可能会引发垃圾收集以卸载类。金额为近似值。第一次达到高水位线后,下一个高水位线由垃圾收集器管理
-XX:MaxMetaspaceSize= 其中 是要为类元数据分配的最大空间量(以字节为单位)。该标志可用于限制为类元数据分配的空间量。该值是近似值。默认情况下没有设置限制。-XX:MinMetaspaceFreeRatio= 其中 是 GC 后可用的类元数据容量的最小百分比,以避免为类元数据分配的空间量(高水位线)增加而导致垃圾收集。
-XX:MaxMetaspaceFreeRatio= 其中 是 GC 后可用的类元数据容量的最大百分比,以避免为类元数据分配的空间量(高水位线)减少而导致垃圾收集。
默认情况下,类元数据分配仅受可用本机内存量的限制。我们可以使用新选项 MaxMetaspaceSize 来限制用于类元数据的本机内存量。它类似于 MaxPermSize。当类元数据使用量达到 MetaspaceSize(32 位客户端 VM 上为 12 MB,32 位服务器 VM 上为 16 MB,64 位 VM 上的大小更大)时,会引发垃圾收集来收集失效的类加载器和类。将 MetaspaceSize 设置为更高的值以延迟诱导的垃圾收集。在诱导垃圾收集之后,诱导下一次垃圾收集所需的类元数据使用量可能会增加。