JBoss 7,java.lang.OutOfMemoryError: PermGen空间错误

27

我遇到了一个错误,导致 CPU 使用率达到极限并且 JBoss 需要重新启动 (java.lang.OutOfMemoryError: PermGen space)。

我找到了一个解决方案,适用于旧版本的 JBoss,可以增加 MaxPermSize 的值。我想对于 JBoss7 也是同样的情况。

哪个值足够好,以便不再遇到此问题?是否有任何方法永久地解决这个问题(比如说使用一个不同的虚拟机,如 JRockit)?


1
除非您部署了许多大类,否则在AS 7上不应该发生这种情况。由于其模块化类加载器,JBoss AS 7不应遭受classloader泄漏,这与测试期间的多次重新部署相结合,是其他服务器上此问题的最常见原因。您只有在多次重新部署后才会出现这些错误吗?在什么情况下? - Craig Ringer
我们一直在测试我们的应用程序,在这个阶段我们进行了大量的重新部署。突然间,这个问题出现了。 - MaVRoSCy
有趣的是,我在我的Windows服务器上使用JBoss 7.1.1作为服务运行。在这种情况下,在部署后它无法启动。如果从standalone.bat运行服务器,则与我的开发机器上相同的效果稳定。 - feder
4个回答

56

由于多次重新部署后出现这种情况,看起来你遇到了一种类加载器泄漏,这是一种常见的永久代内存泄漏

这些可爱的生物发生在容器中拥有对象的正常(非弱)引用指向从应用程序类加载器加载的类的实例的对象。如果这些引用在取消部署时没有清除,则仍存在一个对应用程序类加载器的强引用链,因此无法进行GC,也不能释放已加载的类。

常见的原因是容器类中的静态集合具有对应用程序类添加的非静态引用。

JBoss AS 7在其模块系统中有一些相当强的规定,可以防止类加载器泄漏,因此我很惊讶您已经成功触发了一个。自从我从Glassfish转移到AS7以来,我就没有看到过类加载器泄漏。

增加MaxPermSize会为您赢得一些时间,但它不会解决问题。

你真的需要弄清楚为什么类加载器会泄漏。这很“有趣”。你喜欢交税,间歇性故障和清洁淋浴吧?请参见第一段中的链接以了解一些博客,从而开始跟踪泄漏。基本上,您将想使用VisualVMOQL来挖掘对您的应用程序类加载器的引用,或者获取堆转储并使用jhat(JDK的一部分)查找引用。无论哪种方式,目标都是找出从应用服务器到您的类加载器的强引用链,通过您的应用程序类的实例。

另外,可以通过复制你的应用程序然后逐步删除其中一些部分来帮助解决内存泄漏。通过连接VisualVM或其他监控到应用服务器VM,观察在两个或更多的部署/卸载周期后PermGen是否增加来判断是否存在泄漏问题。考虑自动化部署/卸载周期。将泄漏原因缩小到你的应用程序中的一小部分或一个依赖项,并生成一个小型的、自包含的测试用例,然后将其作为错误报告提交到(a)JBoss AS 7上,因为据我所知,它应该会阻止这种情况发生,以及(b)持有引用的罪魁祸首。

如果你将问题缩小到被捆绑在你的部署归档文件中的某个依赖项上,将其移动到JBoss AS 7模块可能会解决问题。为其创建一个JBoss模块,将其部署到AS7的modules目录中,并通过Manifest.MFjboss-deployment-structure.xml向你的部署添加对其的依赖性。请参阅有关AS7类加载器的文档

这就是为什么推迟项目Jigsaw让我感到难过的原因。Java需要一个强大的模块系统来清除这种冗余。


4
"jhat"指的是Frank Kieviet提出的方法。但这种方法不太方便。更好的方法是:1)生成堆转储文件;2)在Eclipse MAT中打开堆转储文件;3)查找重复的类;4)右键单击最重复的类,然后选择“Merge Shortest Paths to GC Roots”,并选择“exclude weak references”选项。这将列出防止永久代空间通过类卸载而减少的对象列表。 - Julien Kronegg

8

虚拟机参数是:

-XX:MaxPermSize=256M

只要将它设定得足够大,您就不会达到限制。

一般来说,永久代内存用于与类和国际化字符串相关联的对象。除非使用了很多不同的类,否则您不应该用尽它。


你知道JBoss 7的MaxPermSize的默认值吗? - MaVRoSCy
4
虽然有用,但这对类加载器泄漏没有帮助,只是推迟了问题。最终你会耗尽永久代空间。 - Craig Ringer
出于好奇,对于一个Web应用程序来说,什么数量的不同类被认为是很多?100个、1000个还是10000个? - Stinger
1
顺便说一下,我是在 standalone.conf 中完成这个操作的。 - Java Dude

4
我们不得不做完全相同的事情,不幸的是 visualvm 对我们没有什么帮助。最终,我们使用 eclipse mat 分析崩溃时生成的堆转储,并查看了“泄漏嫌疑人”报告,该报告告诉我们有很多 ModuleClassLoader 实例被泄漏了。在总览选项卡中单击其中一个实例,然后选择“合并到 GC 根的最短路径”+“排除弱引用”,我们找到了罪魁祸首,它不允许这些 ModuleClassLoader 实例被 GC 回收!

https://smalldata.tech/blog/2015/09/29/detecting-java-permgen-memory-leak


2
为什么会出现这种情况?
"PermGen"错误是指Java虚拟机在永久代内存用尽时发生的错误。回想一下,Java具有分代垃圾收集器,其中包括四个代:eden、young、old和permanent。在eden代中,对象的生命周期很短,垃圾收集非常快且频繁。young代由在eden代中幸存下来的对象组成(或者因为在分配时eden代已满而被推到young代),young代的垃圾收集不太频繁,但仍然会在相当规律的时间间隔内发生(前提是您的应用程序实际上做了一些事情并且每隔一段时间分配对象)。老年代,好吧,您已经知道了。它包含在young代中幸存下来的对象,或者被推入,并且垃圾收集甚至不太频繁,但仍然可能发生。最后是永久代。这是为虚拟机决定赋予永生的对象而设立的-这正是问题的核心。在永久代中的对象永远不会被垃圾收集;也就是说,在正常情况下使用正常命令行参数启动jvm时不会被垃圾收集。因此,当您重新部署Web应用程序时,您的WAR文件将被解压缩,并将其类文件加载到jvm中。然后问题来了:几乎总是会在永久代中结束...(摘自:http://rlogiacco.blogspot.com/2009/02/jboss-and-permgen-outofmemoryerror.html
以下是一些建议:
使用以下JVM参数。它们告诉垃圾收集器也在PermGen上调用其算法。
set JAVA_OPTS=-Xms512m -Xmx1024m 
-XX:PermSize=512m 
-XX:MaxPermSize=1024m 
-XX:+UseConcMarkSweepGC 
-XX:+CMSPermGenSweepingEnabled 
-XX:+CMSClassUnloadingEnabled 
  • CMSPermGenSweepingEnabled设置包括PermGen在垃圾回收运行中。默认情况下,PermGen空间永远不会被包含在垃圾回收中(因此会无限增长)。
  • CMSClassUnloadingEnabled设置告诉PermGen垃圾回收扫描在类对象上采取行动。默认情况下,即使在垃圾回收期间访问PermGen空间,类对象也会获得豁免。

每次部署应用程序都会增加PermGen中的数据量,因此请重新启动JBOSS。

您还可以使用JRocket JVM而不是Sun JVM。它在其Garbage Collector算法中没有任何PermGen。


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