将SpringMVC应用程序热部署到Tomcat7时的OutOfMemoryError - 与log4j2

时间:2015-07-03 17:31:48

标签: spring-mvc tomcat7 out-of-memory log4j2 hotdeploy

我遇到了热部署Spring-MVC 4.0(不是SpringBoot)Web应用程序的问题。我试图去xml-less并只使用JavaConfig。当我删除web.xml 时会产生 OutOfMemoryErrors,或者当我部署一个只有空元素的空web.xml时 。每次应用程序热部署时都不会发生这种情况,并且在热部署成功后,应用程序可以正常工作,但在使用此配置进行三到四次热部署后,会出现以下错误:

Jul 03, 2015 10:49:43 AM org.springframework.web.context.ContextLoader initWebApplicationContext
    SEVERE: Context initialization failed
    java.lang.OutOfMemoryError: PermGen space
            at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:547)
            at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
            at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303)
            at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
            at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
            at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
            at org.springframework.context.support.PostProcessorRegistrationDelegate.registerBeanPostProcessors(PostProcessorRegistrationDelegate.java:220)
            at org.springframework.context.support.AbstractApplicationContext.registerBeanPostProcessors(AbstractApplicationContext.java:615)
            at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:465)
            at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:403)
            at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:306)
            at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:106)
            at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:5014)
            at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5524)
            at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
            at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901)
            at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
            at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:649)
            at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:1081)
            at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1877)
            at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
            at java.util.concurrent.FutureTask.run(FutureTask.java:262)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
            at java.lang.Thread.run(Thread.java:745)

    Jul 03, 2015 10:49:44 AM org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor run
    SEVERE: Unexpected death of background thread ContainerBackgroundProcessor[StandardEngine[Catalina]]
    java.lang.OutOfMemoryError: PermGen space
            at java.util.concurrent.FutureTask.report(FutureTask.java:122)
            at java.util.concurrent.FutureTask.get(FutureTask.java:188)
            at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:816)
            at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:488)
            at org.apache.catalina.startup.HostConfig.check(HostConfig.java:1655)
            at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:328)
            at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:117)
            at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90)
            at org.apache.catalina.core.ContainerBase.backgroundProcess(ContainerBase.java:1374)
            at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1546)
            at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1556)
            at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1524)
            at java.lang.Thread.run(Thread.java:745)

    Exception in thread "ContainerBackgroundProcessor[StandardEngine[Catalina]]" java.lang.OutOfMemoryError: PermGen space
            at java.util.concurrent.FutureTask.report(FutureTask.java:122)
            at java.util.concurrent.FutureTask.get(FutureTask.java:188)
            at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:816)
            at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:488)
            at org.apache.catalina.startup.HostConfig.check(HostConfig.java:1655)
            at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:328)
            at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:117)
            at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90)
            at org.apache.catalina.core.ContainerBase.backgroundProcess(ContainerBase.java:1374)
            at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1546)
            at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1556)
            at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1524)
            at java.lang.Thread.run(Thread.java:745)

显然,内存会以某种方式泄漏此配置。

此Web应用程序使用Log4j2可能相关也可能不相关。 An earlier Stack Overflow question探讨了这一点。如果Web应用程序仅使用以下最小web.xml

<?xml version="1.0"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
          http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
          version="3.0">

    <context-param>
        <param-name>log4jConfiguration</param-name>
        <param-value>file:///path/to/log4j2.xml</param-value>
    </context-param> 

</web-app>

然后可以反复热部署WebApp而不会出现这些错误。

任何人都可以猜测这里会发生什么吗?

更新 - 请参阅下面@Makoton与我之间的讨论。似乎可能存在与从应用程序(Java Config方式)加载log4j2和从web.xml加载它(传统方式)相关的垃圾收集问题。见this article揭穿经典&#34;经典&#34; Stack Overflow建议解决这个问题(类似于Makoton引用的那个)。

这让我想起SpringBoot,据我所知,它将Tomcat作为应用程序的一部分加载。这可能是解决这个问题的一种方法。

3 个答案:

答案 0 :(得分:2)

我相信这是Log4j2中的一个错误。

我一直在研究我工作的应用程序中类似的PermGen内存泄漏。通过使用分析器,我可以看到当Log4j2关闭时,它没有取消注册它之前注册的所有JMX管理MBean。因为这些MBean留在JVM的内部MBean注册表中,所以未部署的Web应用程序的类无法从内存中完全删除,因为它们引用了这些类的实例。

在你previous question中和我一样,我也看到Log4j2生成了消息

No Log4j context configuration provided. This is very unusual.
启动时

。然而,在我的情况下,修复有点奇怪 - 网络应用程序没有在我们的web.xml中设置<display-name>,并给它一个显示名称,使该消息和内存泄漏消失!

我怀疑Log4j2中存在一个错误,如果没有它提到的上下文,它就不会注销它注册的MBean。传递显示名称是创建足够上下文的一种方法,为配置位置添加上下文参数似乎是另一种。 如果还没有问题,我会考虑为此提出一个问题。如果缺少显示名称,目前有an issue on the Log4j2 JIRA提到问题,我现在添加了评论对于这个问题,提到在这种情况下也存在内存泄漏。

据我所知,你有三种选择。

  1. 使用web.xml文件。它可能只需要<display-name>
  2. 通过将系统属性log4j2.disable.jmx设置为true来禁用Log4j2中的JMX。 (最好使用命令行参数-Dlog4j2.disable.jmx=true完成。)Log4j2 JMX documentation中提到了此选项。
  3. 查看Mattias Jiderhamn's ClassLoaderLeakPreventor库。 Web应用程序关闭期间的一个功能是取消注册Web应用程序注册但未注销的任何MBean。您将无法直接将该库用作JAR文件,因为它需要作为侦听器添加到web.xml中,大概是为了那些仍在使用旧版Servlet版本的人的利益。但是,有一些方法可以解决这个问题。

    首先,there is only one .java file in this library,如果您还在类中添加@WebListener注释,则可以在应用程序中包含其源(或仅包含其注册MBean的源的部分)。其次,您可以扩展ClassLoaderLeakPreventor类并将@WebListener注释添加到子类中。

答案 1 :(得分:1)

很久以前我对tomcat7热部署有同样的问题。当我开始使用JAVA_OPTS

  

-XX:+ CMSClassUnloadingEnabled

解决我的问题。我想解释一下,但我认为这是更好的解释。

What does JVM flag CMSClassUnloadingEnabled actually do?

http://frankkieviet.blogspot.ca/2006/10/classloader-leaks-dreaded-permgen-space.html

答案 2 :(得分:1)

我相信卢克是对的,这个问题可能是由a bug in Log4j2造成的。

错误是如果web.xml中不存在<display-name>元素,则负责log4j资源管理的Log4jServletContextListener类只能启动,但不能停止log4j。

这意味着停止或重新启动Web应用程序时不会进行清理。 JMX MBean未注销,但也没有停止任何线程,因此不会卸载Log4j类。每次重新启动Web应用程序时,泄漏的内存都会增长,因为每个Web应用程序都有自己的类加载器(因此VM将它们视为不同的类)。

此错误现已在Git master中修复,修复程序将成为log4j 2.5版本的一部分。同时,请在您的web.xml中使用<display-name>元素。