内存溢出错误:PermGen空间 -- 在Tomcat上运行的Spring Jasper报告

8
我们的Web应用程序遇到了一个复杂的情况。
它是由STS/Tomcat 7开发的Spring应用程序。在集成了Jasper report 4.6.0之后,它总是抛出"OutOfMemoryError: PermGen Space"的异常。然后唯一的方法是重启应用程序。但过了一段时间后,又会再次发生。 这是异常之前的日志:
Oct 17, 2012 3:42:27 PM org.apache.jasper.compiler.TldLocationsCache tldScanJar
INFO: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
Oct 17, 2012 3:42:30 PM org.apache.catalina.core.ApplicationDispatcher invoke
SEVERE: Servlet.service() for servlet jsp threw exception

这是异常中的一个部分,我在这里发现了一些关于 Jasper 的内容:
at org.apache.jasper.compiler.JDTCompiler.generateClass(JDTCompiler.java:442)
at org.apache.jasper.compiler.Compiler.compile(Compiler.java:378)
at org.apache.jasper.compiler.Compiler.compile(Compiler.java:353)
at org.apache.jasper.compiler.Compiler.compile(Compiler.java:340)
at org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:646)
at org.apache.jasper.servlet.JspServletWrapper.loadTagFile(JspServletWrapper.java:240)
at org.apache.jasper.compiler.TagFileProcessor.loadTagFile(TagFileProcessor.java:578)
at org.apache.jasper.compiler.TagFileProcessor.access$000(TagFileProcessor.java:49)
at org.apache.jasper.compiler.TagFileProcessor$TagFileLoaderVisitor.visit(TagFileProcessor.java:655)

以下是当出现这种情况时的几点发现:
  1. The issue can happen on page without any Jasper Report components. It seems the Jasper Report bean is trying to find a tag lib all the time when a request is processed by the back end and responded to the front end. Normally from log file I can see above exception will not throw until all back end operations(JPA management) are complete

  2. When run log4J on debug mode, I see tons of information showing something like parsing/rendering the all components from Jasper template(textfields, pen, box...), there is a small cut from the huge log:

    2012-10-17 15:29:12,025 -- DEBUG -- org.apache.commons.digester.Digester.sax -- startElement(http://jasperreports.sourceforge.net/jasperreports,textElement,textElement)
    2012-10-17 15:29:12,025 -- DEBUG -- org.apache.commons.digester.Digester --   Pushing body text ''
    2012-10-17 15:29:12,025 -- DEBUG -- org.apache.commons.digester.Digester --   New match='jasperReport/summary/band/textField/textElement'
    2012-10-17 15:29:12,025 -- DEBUG -- org.apache.commons.digester.Digester --   Fire begin() for FactoryCreateRule[className=net.sf.jasperreports.engine.xml.JRTextElementFactory, attributeName=null, creationFactory=net.sf.jasperreports.engine.xml.JRTextElementFactory@12dc6007]
    2012-10-17 15:29:12,025 -- DEBUG -- org.apache.commons.digester.Digester -- [FactoryCreateRule]{jasperReport/summary/band/textField/textElement} New net.sf.jasperreports.engine.design.JRDesignTextField
    2012-10-17 15:29:12,025 -- DEBUG -- org.apache.commons.digester.Digester.sax -- ignorableWhitespace()
    

    Still, this log is generated when a request to the page which does not contains any Jasper component.

我做了一些调查,但仍然找不到解决此问题的方法。
1.首先问题是:即使应用程序中有一个jasperreport bean,为什么它总是在运行时,即使当前页面没有任何jasper组件也没有进行自动装配。对于这种情况是否有解决/答案?
2.从异常消息中得知“至少扫描了一个JAR以获取TLD,但未包含任何TLD”,应该来自于Tomcat,并且“Tomcat从不包含任何JSTL jar”,因此我认为它无法找到匹配的TLD以解析jasper报告,因此会对所有jar进行全面扫描。如果是这样,那么为什么会有大量来自org.apache.commons.digester.Digester的调试日志实际上似乎正在忙于解析jasper模板?
一般来说,本贴只是尝试找出解决问题的方法,以及找出为什么Jasper在不需要它的地方如此活跃,以及我们如何让Tomcat正确解析模板?
如果太啰嗦,请见谅,并感谢任何提示。

那么,你尝试过增加PermGen的大小吗?好像没有太多其他的方法了。 - Frank Pavageau
@Frank Pavageau 是的,我尝试了,将其设置为512M,但仍然遇到相同的问题...这是在catalina.sh和JAVA_OPTS="...-XX:MaxPermSize=512m..."中吗? - Dreamer
1
@FrankPavageau,看起来Jasper报告在某些配置方面存在这种问题,因此在Jasper报告中排除根本原因可能是可行的。 - eis
@eis,请为我解释一下如何在Jasper Report中跟踪根本原因,或者我应该尝试使用HeapDumpOnOutOfMemoryError吗?谢谢。 - Dreamer
@Dreamer 你可以尝试获取错误条件的堆转储并进行分析,这应该是向前迈出的一步。 - eis
5个回答

9
感谢大家对这个问题提供的解决方案,我已经针对我的情况确定了问题,并找到了解决方法: 使用.jasper而不是.jrxml作为模板! 我们知道,.jasper是编译后的模板,而.jrxml是模板的ASCII源代码。因此,如果我们在当前的Spring应用程序中使用原始源代码文件(jrxml)作为模板,那么至少Spring框架必须编译源代码文件。这是一个效率问题,留给Spring框架处理,因为它是处理编译的jasper bean,不能保证编译只执行一次,也不能保证仅在应用程序启动时执行。
简而言之,在用.jasper文件替换所有模板后,日志大小显著减小,并且再也没有看到内存不足的问题。我猜测Spring容器可能会消耗大量资源在运行时将jrxml编译成jasper。因此,这可能是Jasper或Spring应该改进的地方...

7

当JVM的permgen空间中有太多的.class文件时,会出现异常,由于其对AppClassLoader之外的对象的引用而无法进行垃圾回收。它通常指向应用程序中的某些内存泄漏。

这篇文章清晰地解释了java.lang.OutOfMemoryError:PermGen space错误,下一篇文章提供了如何解决该错误的建议。如果您错过了类似(但不完全相同)的问题在SO上被问到了,我希望它能帮助到您。

正如Jakub所提到的,设置-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled或将XX:MaxPermSize的值设置得更高可能对您有用。但根据我所读的,似乎这不是一个永久性的解决方案。(我并不是专家 :))。


1
链接上的帖子很好。如果有人需要了解正在发生的事情,最好阅读这些帖子。谢谢。 - Marouane Lakhal

2
我开发了一个使用JasperReports 4.5.1的Web应用程序。
我使用Tomcat 6.0.26作为容器。(Win7,JDK 1.6.0_25)
当关闭Tomcat时,会抛出以下错误:
该Web应用程序创建了一个ThreadLocal,其键类型为[net.sf.jasperreports.engine.util.JRFontUtil$1](值为[net.sf.jasperreports.engine.util.JRFontUtil$1@7892f1]),值类型为[java.util.HashSet](值为[[]]),但在Web应用程序停止时未能将其删除。这很可能会导致内存泄漏。
请访问网站:

http://community.jaspersoft.com/questions/534340/memory-leak-jr-373


2

尝试在您的虚拟机中设置这些参数。这些参数应该使GC清理您的permGen。

-XX:+UseConcMarkSweepGC
-XX:+CMSPermGenSweepingEnabled
-XX:+CMSClassUnloadingEnabled

谢谢。你确定我们要对JVM而不是Tomcat进行这个操作吗?这种更改会对性能产生影响吗? - Dreamer
PermGen OOM通常是因为GC运行但无法释放内存,而不是因为GC没有处理PermGen。 - Frank Pavageau
1
为了真正诊断问题,您应该添加-XX:+ HeapDumpOnOutOfMemoryError,这样您就可以获得堆转储。不确定它是否适用于permgen错误,但我建议尝试一下。然后,应使用MAT或类似工具运行该堆转储。 - eis
是的,您应该在Tomcat配置中设置这些参数,这样当它为您的应用程序启动VM时,它就会使用这些参数。我们正在使用GlassFish,并没有注意到任何性能影响,只是PermGen错误不再显示。 - jakub.petr

0

由于PermGen主要包含类元数据、常量和interned字符串,因此您可以沿两个方向进行搜索:

  • 检查Web应用程序是否不包含(太多)无用的JAR文件,这可能会由于扫描而加载

  • 使用堆转储查看是否有大量常量字符串(例如大量的JSP),或者您的代码是否使用String.intern()


实际上,您没有指定使用的Java版本:使用Java 7,String可能不是问题。

您可以使用VisualGC插件使用JVisualVM观察应用程序,以查看代的状态、加载的类数以及在OOM时是否有任何一个激增或者缓慢的累积。


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