java – 了解Groovy / Grails类加载器泄漏

昨天我将我的第一个Grails(2.3.6)应用程序部署到开发服务器并开始监控它.我刚刚得到一个自动监视器,说明CPU被固定在这台机器上,所以我连接到它.我跑了顶,发现是我的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特别麻烦?

解决方法:

Groovy是一种动态语言,每个方法调用都是动态调度的.为了优化Groovy,为MetaClassRegistry中的每个java.lang.Class创建一个MetaClass.这些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.parseClass或GroovyShell.evaluate在运行时编译类.如果你不断解析类,那么添加类卸载标志可以提供帮助.另见这篇文章:

Locating code that is filling PermGen with dead Groovy code

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

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

上一篇:使用Java ScriptEngine(Groovy),如何使其更高效?


下一篇:java – 如何为所有控制器配置默认的@RestController URI前缀?