了解Groovy / Grails类加载器泄漏

时间:2014-06-11 18:25:36

标签: java grails memory-leaks groovy classloader

昨天我将我的第一个Grails(2.3.6)应用程序部署到开发服务器并开始监控它。我刚刚得到一个自动监视器,说明CPU被固定在这台机器上,所以我连接到它。我跑了top并发现这是我的Java应用程序的PID,它固定了服务器。我也注意到内存是40%。几秒钟后,CPU停止固定,降至正常水平,内存恢复到~20%范围内。经典主要GC。

在收集的同时,我做了堆转储。在GC之后,我在JVisualVM中打开了转储,并看到大部分内存都是为org.codehaus.groovy.runtime.metaclass.MetaMethodIndex.Entry类分配的。总共有近250,000个实例,占用了大约25 MB的内存。

我用Google搜索了这个课程并查看了ultra helpful Javadocs。所以我仍然不知道这堂课做了什么。

但谷歌搜索它也带来了大约十几篇涉及这个类的相关文章(其中一些是SO问题)和Grails / Groovy应用程序的PermGen / classloader泄漏。虽然看起来我的应用程序确实用GC清理了这些250K实例,但仍然令人不安的是它有如此多的实例,并且GC将CPU固定超过5分钟。

我的问题:

  • 这个课程是什么,Groovy用它做什么?
  • 有人可以向我解释this answer吗?为什么-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled会帮助解决这个问题?
  • 为什么这门课对PermGen来说特别麻烦?

1 个答案:

答案 0 :(得分:13)

Groovy是一种动态语言,每个方法调用都是动态调度的。要优化Groovy,请为MetaClass中的每个java.lang.Class创建MetaClassRegistry。这些MetaClass实例是按需创建的,并使用弱引用存储。

您看到很多org.codehaus.groovy.runtime.metaclass.MetaMethodIndex.Entry的原因是因为Groovy在内存中存储了类和方法的映射,以便运行时可以快速分派它们。根据应用程序的大小,这可能是因为您已经发现了数千个类,因为每个类可能有数十个类,有时可能有数百个方法。

但是,Groovy和Grails中没有“内存泄漏”,你看到的是正常行为。您的应用程序内存不足,可能是因为它没有分配足够的内存,这反过来会导致MetaClass实例被垃圾回收。现在说比如你有一个循环:

for(str in strings) {
   println str.toUpperCase()
}

在这种情况下,我们在String类上调用一个方法。如果内存不足,将会发生的事情是,对于循环的每次迭代,MetaClass将被垃圾收集,然后再次重新创建以用于下一次迭代。这可能会大大减慢应用程序的速度,并导致CPU被固定,如您所见。这种状态通常被称为“元类流失”,是应用程序在堆内存上运行不足的标志。

如果Groovy 不是垃圾收集这些MetaClass实例,那么是的,这意味着Groovy中存在内存泄漏,但垃圾收集这些类的事实是一个标志,一切都很好,除了你没有分配足够的堆内存的事实。这并不是说应用程序的另一部分可能会占用内存泄漏而占用所有可用内存并且不足以让Groovy正常运行。

至于你提到的另一个答案,添加类卸载和PermGen调整实际上不会做任何事情来解决你的内存问题除非你在运行时动态解析类。 JVM使用PermGen空间来存储动态创建的类。 Groovy允许您使用GroovyClassLoader.parseClassGroovyShell.evaluate在运行时编译类。如果你不断解析类,那么添加类卸载标志可以提供帮助。另见这篇文章:

Locating code that is filling PermGen with dead Groovy code

但是,典型的Grails应用程序不会在运行时动态编译类,因此调整PermGen和类卸载设置实际上不会实现任何效果。

您应该验证是否使用-Xmx标志分配了足够的堆内存,如果没有分配更多内存。