了解Groovy/Grails类加载器泄漏

IAm*_*aja 10 java grails groovy memory-leaks 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搜索了这个课程并看了一下它是非常有帮助的Javadocs.所以我仍然不知道这门课做了什么.

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

我的问题:

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

Gra*_*her 13

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

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

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

for(str in strings) {
   println str.toUpperCase()
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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