我遇到了热部署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作为应用程序的一部分加载。这可能是解决这个问题的一种方法。
答案 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提到问题,我现在添加了评论对于这个问题,提到在这种情况下也存在内存泄漏。
据我所知,你有三种选择。
<display-name>
。log4j2.disable.jmx
设置为true
来禁用Log4j2中的JMX。 (最好使用命令行参数-Dlog4j2.disable.jmx=true
完成。)Log4j2 JMX documentation中提到了此选项。查看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>
元素。