Java使用Web应用程序时出现java.lang.OutOfMemoryError: PermGen空间错误。

11

我在处理最近出现的一个内存溢出PermGen问题时遇到了困难。以下是出现错误时保存的日志片段:

java.lang.OutOfMemoryError: PermGen space
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
        at org.apache.felix.framework.ModuleImpl$ModuleClassLoader.findClass(ModuleImpl.java:1872)
        at org.apache.felix.framework.ModuleImpl.findClassOrResourceByDelegation(ModuleImpl.java:720)
        at org.apache.felix.framework.ModuleImpl.access$300(ModuleImpl.java:73)
        at org.apache.felix.framework.ModuleImpl$ModuleClassLoader.loadClass(ModuleImpl.java:1733)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:248)

我增加了最大永久代大小-XX:MaxPermGen=128m,但这只是一个临时解决方案,因为我相信我们面临内存泄漏问题。我们应用程序的Web部分部署在Jetty上(JSF + Icefaces)。单击随机组件会增加使用的内存-我正在使用jstat -gcold进行监视,几乎每次点击都意味着增加3-4KB。我将-XX:+TraceClassLoading添加到JVM参数中,并在Web用户界面发生任何操作时看到许多sun.reflect.GeneratedConstructorAccessorsun.reflect.GeneratedMethodAccessor被记录。当永久代使用了99%时,我还进行了堆转储。我使用YourKit分析器来分析堆。在类加载器选项卡中,有许多sun.reflect.DelegatingClassLoader行,每个行对应一个类。什么可能导致内存不断增长?任何帮助将不胜感激。

提前致谢, Lukasz


在你增加它之前,MaxPermGen的值是多少? - Shervin Asgari
JVM 使用了默认值,即 84MB。 - Lukasz
5个回答

5
在Sun JVM上,对属性和方法的反射访问最初是通过JNI调用JVM实现进行的。如果JVM注意到一个方法或字段被反射访问很多次,它将生成字节码来执行相同的操作——这个机制称为"inflation"。首次速度较慢,但在此之后速度大约提高了20倍。如果您经常使用反射,这是一个巨大的优势。
该字节码存储在由DelegatingClassLoader实例创建的类中,并占用permgen空间。如果这是一个问题,您可以通过将系统属性sun.reflect.inflationThreshold设置为0(零)来关闭inflation。

4
首先,这与JSF无关。相比于您的Web应用程序的需求,您只是为应用服务器分配了太少的内存。您在使用基于反射的其他框架时也会遇到同样的问题(想想所有这些EL解析)。这可能是JSF、Wicket、Spring-MVC甚至纯JSP/Servlet。组件化的Web框架依赖于EL解析器,如JSF(和其他框架),机会更大。
此外,已知Tomcat(基于)服务器可能在您(hot)重新部署得太频繁时也会导致这种情况。请通过以下链接了解此问题的原因以及如何处理:

我有一种奇怪的感觉,无论我为永久代分配多少内存,它仍然能够堵塞它 - 至少它的行为方式让我这样认为。应用程序部署后,永久代使用了60%。通过所有组件(以便基本上加载了所有类)的运行,将permgen使用情况提高到约65%。然后,借助Squish自动化测试的帮助,我很容易将使用率增加到近80%。我猜所有基于反射加载的类都是异常行为,只是不知道为什么它们没有被卸载。 - Lukasz
这些事情总是让我想知道:.NET是否也有同样的问题?他们使用分代GC算法;他们的字符串是不可变的;他们有反射。他们是否感受到我们的痛苦?他们会采取什么措施?我曾经参与开发过一个应用程序,部署在WebLogic上,从未需要服务器重启:没有泄漏,没有连接丢失。难道应用服务器是唯一的区别吗? - duffymo
@Lukasz:JSF在会话中序列化有限数量的视图(默认为15,可以通过上下文参数进行配置)。一旦使用了所有可能的表达式,它将变得稳定。您只需要确保服务器具有足够的内存来处理即可。例如,使用JMeter进行100个并发用户的压力测试以了解边界情况。@Duffymo:我对.NET或Weblogic一无所知,但我知道IIS基本上没有软件内存限制,它基本上是自由使用Windows可用内存。Weblogic很可能有更高的默认内存限制(如Glassfish)。 - BalusC
@Lukasz:问题在于你有相对复杂的JSF视图,而Tomcat默认情况下内存限制相对较低。所有这些都需要根据压力测试进行调整/对齐。 - BalusC

3
我建议使用Eclipse MAT并遵循这个专门针对permgen问题的教程
尽管duffymo注意到你已经很好地分析了问题,但问题的根源似乎仍然不明确。也许只是其中一个组件,一些解析器(如jwenting所建议的)或类似的东西。这个问题并不一定意味着你需要从堆栈中删除JSF。

我也尝试了MAT,但它基本上与YourKit显示的内容相同——有许多sun.reflect.DelegatingClassLoader条目。 - Lukasz

1
首先,向您致敬,感谢您对此进行了如此彻底的调查,并以更好的方式撰写了您的问题。如果每个人都像您一样有才华且是一位出色的作家,那么这个世界(和这个网站)将会变得更美好。
我认为您已经找到了答案:我认为JSF及其使用反射是您的问题所在。
这就是为什么我像瘟疫一样避免使用JSF的原因之一。
在我看来,JSF是Struts的失败扩展。如果不是更加耗费JVM资源,我会认为HTML/CSS/JavaScript/AJAX UI与JSF一样具备能力,甚至更具备能力。UI调用服务并保持与服务器端的良好分离。

谢谢,不过我们现在离改变Web框架太远了,甚至无法考虑。 - Lukasz
错误的印象。在JSF出现之前,PermGen问题就已经存在了。Struts/Tiles 1.x系列存在PermGen泄漏问题,其他框架也有类似情况。有各种文章介绍不同的原因,其中一个主要点是孤立的对象/类,由于与其他类加载器构件的链接而无法进行垃圾回收。因此,你会遇到鸡生蛋的问题,导致类/对象无法被GC并造成内存泄漏。请搜索相关文章,查看你的代码或任何第三方代码是否有责任。 - him
不是虚假的印象。当然,即使没有JSF,也可能发生永久代问题。我只是碰巧观察到一个特别糟糕的JSF实现,使得页面刷新可以用日晷来计时。我建议像瘟疫一样避免它。我喜欢那些新来Stack Overflow的人决定把他们的时间浪费在对1.5年前的问题发表无聊的评论上。 - duffymo

0

Permgen问题通常是由某些进程执行大量的String.intern()操作引起的。 一些XML/HTML生成器和解析器会导致这种问题。 首先检查这些,你可能很快就能找到罪魁祸首。


我们的应用程序不使用它们,也许一些第三方会使用 - 我们会看一眼,谢谢。 - Lukasz

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