我正在加载大量Groovy (2.4.6)脚本,并在我的Java8应用程序中使用GroovyScriptEngineImpl运行它们,过了一段时间,我遇到了一个问题。
有几件事你需要知道:
GroovyScriptEngineImplGroovyClassLoader为了在一个单独的“环境”中隔离每个脚本,我需要这样做:我在类加载器中为某些脚本加载一些外部JAR,我不希望其他脚本在执行这些JAR时能够使用这些类。
我的问题来自这样一个事实:对于我运行的每一个脚本,GroovyClassLoader都会创建一个新的ScriptXXXX类并加载它,但是永远不会卸载它。
这会导致加载的类数量不确定地增加,并且内存最终完全被填满。
我尝试了大量的各种解决方案,但似乎都没有效果:
-XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC-Dgroovy.use.classvalue=trueScriptXXXX类创建的“元类”,如下所示:Groovy类没有被收集,但没有内存泄漏的迹象GroovyClassLoaderGroovyScriptEngineImpl中缓存类的某些字段下面是Eclipse中的一个ScriptXXXX类的“到GC的最短路径”:

我在这里显然没有解决方案了,而且似乎没有一个真正起作用,因为类加载器总是保持对那些永远得不到GCed的类的引用。
如果您想重现这个问题,下面是一个代码示例:
GroovyScriptEngineImpl se;
while (true)
{
se = new GroovyScriptEngineImpl(new GroovyClassLoader());
CompiledScript script = se.compile("println(\"hello\")");
script.eval(se.createBindings());
}谢谢
UPDATE:在阅读了pczeus的答复之后,我尝试限制metaspace,有些类似乎确实在卸载,我认为是ScriptXXX类。
也就是说,几分钟后,我将在脚本执行过程中得到Out of Metaspace错误。
下面是我在VisualVM上得到的个人资料:

ScriptXXX类的Eclipse中的“到GC的路径”确实是空的(它们不再是类的实例),甚至类仍然列在直方图中。
发布于 2016-05-22 17:10:35
这显然是一种边缘情况,需要进行特殊的调优,并可能选择使用的垃圾收集方案。
由于您使用的是Java8,而且您知道您正在加载数千个临时类,所以您应该尝试优化和限制可用的MetaSpace数量,并优化清理的频率。直接从永久的:
JDK8: Metaspace 在JDK 8中,类元数据现在存储在本机堆中,这个空间称为Metaspace。JDK 8中为Metaspace添加了一些新标志: -XX:MetaspaceSize=,其中是分配给类元数据(以字节为单位)的初始空间量(初始高水标记),这些空间可能导致垃圾收集卸载类。这个数额是近似的。在首次到达高水标记之后,下一个高水标记将由垃圾收集器管理。 -XX:MaxMetaspaceSize=,其中是为类元数据分配的最大空间(以字节为单位)。此标志可用于限制为类元数据分配的空间。这个值是近似的。默认情况下没有设置限制。-XX:MinMetaspaceFreeRatio=是在GC之后释放类元数据容量的最小百分比,以避免为类元数据分配的空间(高水标记)增加,从而导致垃圾收集。 -XX:MaxMetaspaceFreeRatio=是在GC之后释放类元数据容量的最大百分比,以避免减少分配给类元数据的空间(高水标记),从而导致垃圾收集。 默认情况下,类元数据分配仅受可用本机内存量的限制。我们可以使用新的选项MaxMetaspaceSize来限制用于类元数据的本机内存量。它类似于MaxPermSize。当类元数据使用达到MetaspaceSize时(32位客户端VM上的12兆字节和64位VM上较大大小的32位服务器VM上的16兆字节),就会引入垃圾收集来收集死类加载器和类。将MetaspaceSize设置为更高的值,以延迟诱导的垃圾回收。在诱导垃圾收集之后,诱导下一个垃圾收集所需的类元数据使用量可能会增加。
https://stackoverflow.com/questions/37301117
复制相似问题