定位填充PermGen的死Groovy代码的代码

10
我们的GlassFish实例每两周会出现一个java.lang.OutOfMemoryError:PermGen space错误而崩溃。我将PermGen空间增加到512MB,并使用jstat -gc开始转储内存使用情况。两周后,我得到了以下图表,显示PermGen空间稳步增加(x轴单位为分钟,y轴为KB)。 Graph of increasing PermGen usage 我尝试在谷歌上寻找某种性能分析工具来定位错误,并在此SO线程中提到的jmap证明非常有用。从jmap -permstats $PID转储的大约14000行中,大约有12500行包含groovy/lang/GroovyClassLoader$InnerLoader,指向我们自己的Groovy代码或Groovy本身存在某种内存泄漏。我必须指出,Groovy只占相关代码库的不到1%。
以下是示例输出:
class_loader    classes bytes   parent_loader   alive?  type

<bootstrap> 3811    14830264      null      live    <internal>
0x00007f3aa7e19d20  20  164168  0x00007f3a9607f010  dead    groovy/lang/GroovyClassLoader$InnerLoader@0x00007f3a7afb4120
0x00007f3aa7c850d0  20  164168  0x00007f3a9607f010  dead    groovy/lang/GroovyClassLoader$InnerLoader@0x00007f3a7afb4120
0x00007f3aa5d15128  21  181072  0x00007f3a9607f010  dead    groovy/lang/GroovyClassLoader$InnerLoader@0x00007f3a7afb4120
0x00007f3aad0b40e8  36  189816  0x00007f3a9d31fbf8  dead    org/apache/jasper/servlet/JasperLoader@0x00007f3a7d0caf00
....

那么我该如何查找导致此问题的代码呢?从这篇文章中,我推断我们的Groovy代码在某处动态地创建类。从jmap的转储中,我可以看到大多数死对象/类(?)有相同的parent_loader,尽管我不确定在这种情况下这意味着什么。我不知道接下来该怎么做。

补充说明

对于后来者,值得指出的是,被接受的答案并没有解决问题。它只是通过不存储太多的类信息来延长了重启所需的时间。实际上解决我们问题的是消除生成它的代码。我们使用了验证(根据契约设计)框架OVal,其中可以使用Groovy作为方法和类的注释来编写自定义约束脚本。将注释改为明确规定的Java代码虽然很无聊,但它完成了工作。我怀疑每次检查OVal约束时都会创建一个新的匿名类,并且相关的类数据会导致内存泄漏。


这个解决方案有什么好处吗:https://dev59.com/v3VD5IYBdhLWcg3wGXlI - Michael
我还没有解决这个问题 - 其他事情更紧迫,因为这个问题可以通过每两周重启来“解决”,但是我发现使用Eclipse Memory Analyzer定位是什么导致类加载器泄漏的一些好技巧:http://sites.google.com/site/eclipsebiz/The-Unknown-Generation-Perm - oligofren
当我有时间倾倒堆空间并分析它时,我会回来检查。似乎在互联网上很少有解决方案。 - oligofren
2个回答

3

我们曾经遇到过类似的问题(每隔一周就会崩溃)。问题看起来是由于Groovy缓存元方法导致的。最终,我们采用了基于这个讨论错误报告的代码。

GroovyClassLoader loader = new GroovyClassLoader();
Reader reader = new BufferedReader(clob.getCharacterStream());
GroovyCodeSource source = new GroovyCodeSource(reader, name, "xb3.Classifier");
Class<?> groovyClass = loader.parseClass(source);
Object possibleClass = groovyClass.newInstance();
if (expectedType.isAssignableFrom(possibleClass.getClass())) {
    classifiers.put((T) possibleClass, name);
}
reader.close();
// Tell Groovy we don't need any meta
// information about these classes
GroovySystem.getMetaClassRegistry().removeMetaClass(possibleClass.getClass());
// Tell the loader to clear out it's cache,
// this ensures the classes will be GC'd
loader.clearCache();

1
谢谢!我会试一下。可能要等一段时间才能回来点赞或回答,但不会忘记的 :) - oligofren
终于有时间来检查这个了。将PermGen增加从大约100KB/min降至10KB/min(使用VisualVM的近似测量)。这至少可以让我们在每次重启之间多几周。只需要在脚本解析后添加以下内容:GroovySystem.getMetaClassRegistry().removeMetaClass(script.getClass()); - oligofren
1
对于旁观者而言,上述错误报告将修复版本定为2008年的1.5.7版。 - fionbio

-6
如果您正在使用Sun JVM,请更换为IBM JVM,我希望它能正常工作 :)

谢谢你的建议,Eric,但我不知道那怎么会有帮助。我知道IBM JVM没有像JVM一样预设静态限制PermSpace,所以它会随着需要逐渐增加它。 - oligofren
但是,就我所知,那仍然无法解决我正在经历的内存泄漏问题。 - oligofren
IBM的JVM进行了优化,源代码不仅限于这些。你的内存泄漏是由外部资源占用内存所引起的。 - EricParis16
我们已经知道了,但是我该如何找出哪部分代码没有释放Groovy Classloader呢?有些东西正在保留对它的引用,我需要通过内存转储或类似方式分析运行系统来找到它。我已经使用jmap找到了污染永久空间的类,但我需要更深入地挖掘。很遗憾,我无法通过stackoverflow披露我们20MB的源代码 :-) - oligofren

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接