在Servlet上下文重载时,WebApplicationContext没有被关闭

时间:2022-08-22 13:42:33

When I shut down Tomcat, I observe a correct shutdown and cleanup of the Spring WebApplicationContext. However, when I redeploy my Spring-based WAR (by copying the new WAR to webapps), normal shutdown does not occur. This is a problem for me due to all the ensuing resource leaks:

当我关闭Tomcat时,我观察到Spring WebApplicationContext的正确关闭和清理。然而,当我重新部署基于spring的WAR(通过将新的WAR复制到webapps)时,正常的关闭不会发生。这对我来说是一个问题,因为随之而来的资源泄露:

org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [] appears to have started a thread named [hz.hazelcast-swipe-instance.scheduled] but has failed to stop it. This is very likely to create a memory leak.
org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [] appears to have started a thread named [hz.hazelcast-swipe-instance.operation.thread-0] but has failed to stop it. This is very likely to create a memory leak.

... and many more. I am using XML-less configuration, this is my WebApplicationInitializer:

…和许多更多。我使用的是无xml配置,这是我的WebApplicationInitializer:

public class WebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{
  @Override protected Class<?>[] getRootConfigClasses() {
    return new Class[] { WebSecurityConfig.class, WebMvcConfig.class };
  }
  @Override protected Class<?>[] getServletConfigClasses() { return null; }

  @Override protected String[] getServletMappings() { return new String[] { "/" }; }

  @Override public void onStartup(ServletContext ctx) throws ServletException {
    ctx.setInitParameter("spring.profiles.active", "production");
    super.onStartup(ctx);
  }
}

There is no configuration specific to controlling the behavior upon servlet context reload, and I assume this should have worked out of the box.

没有特定的配置来控制servlet上下文重载时的行为,我假设这应该是开箱即用的。

Is there a way to make the WebApplicationContext close properly before continuing the servlet context reloading procedure?

在继续servlet上下文重载过程之前,是否有一种方法可以使WebApplicationContext正确关闭?

I am on Spring 4.0.5, Tomcat 7.0.54, Hazelcast 3.2.1, Hibernate 4.3.4.Final.

我在Spring 4.0.5, Tomcat 7.0.54, Hazelcast 3.2.1, Hibernate 4.3.4.Final。

Update

I have added a Spring application listener for the ContextClosedEvent and printed the stack trace of its invocation:

我为ContextClosedEvent添加了一个Spring应用程序侦听器,并打印其调用的堆栈跟踪:

    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:333) [spring-context-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:335) [spring-context-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:880) [spring-context-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.close(AbstractApplicationContext.java:841) [spring-context-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.springframework.web.servlet.FrameworkServlet.destroy(FrameworkServlet.java:819) [spring-webmvc-4.0.6.RELEASE.jar:4.0.6.RELEASE]
    at org.apache.catalina.core.StandardWrapper.unload(StandardWrapper.java:1486) [catalina.jar:7.0.54]
    at org.apache.catalina.core.StandardWrapper.stopInternal(StandardWrapper.java:1847) [catalina.jar:7.0.54]
    at org.apache.catalina.util.LifecycleBase.stop(LifecycleBase.java:232) [catalina.jar:7.0.54]
    at org.apache.catalina.core.StandardContext.stopInternal(StandardContext.java:5647) [catalina.jar:7.0.54]
    at org.apache.catalina.util.LifecycleBase.stop(LifecycleBase.java:232) [catalina.jar:7.0.54]
    at org.apache.catalina.core.ContainerBase$StopChild.call(ContainerBase.java:1575) [catalina.jar:7.0.54]
    at org.apache.catalina.core.ContainerBase$StopChild.call(ContainerBase.java:1564) [catalina.jar:7.0.54]

This indicates that the Spring shutdown occurs in its Servlet#destroy method. This is the relevant snippet from AbstractApplicationContext#close():

这表明Spring关机发生在它的Servlet#destroy方法中。这是AbstractApplicationContext#close()中的相关代码片段:

        if (logger.isInfoEnabled()) {
            logger.info("Closing " + this);
        }

        LiveBeansView.unregisterApplicationContext(this);

        try {
            // Publish shutdown event.
            publishEvent(new ContextClosedEvent(this));
        }
        catch (Throwable ex) {
            logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
        }
        // Stop all Lifecycle beans, to avoid delays during individual destruction.
        try {
            getLifecycleProcessor().onClose();
        }
        catch (Throwable ex) {
            logger.warn("Exception thrown from LifecycleProcessor on context close", ex);
        }

        // Destroy all cached singletons in the context's BeanFactory.
        destroyBeans();

        // Close the state of this context itself.
        closeBeanFactory();

        // Let subclasses do some final clean-up if they wish...
        onClose();

        synchronized (this.activeMonitor) {
            this.active = false;
        }

I see the log entry from the start of this snippet, and I get my ContextClosedEvent. I also see an entry DefaultLifecycleProcessor - Stopping beans in phase 2147483647, which probably comes from the getLifecycleProcessor.onClose() line. It seems that some error occurs downstream from that. Some exception may be swallowed.

我从这段代码的开头看到日志条目,并获得ContextClosedEvent。我还看到一个条目DefaultLifecycleProcessor—在阶段2147483647中停止bean,它可能来自getlifecycleprocess . onclose()行。似乎有一些错误发生在下游。有些例外可能被忽略。

Update 2

As requested, this is how I configure Hazelcast:

按照要求,我是这样配置Hazelcast的:

@Bean(destroyMethod="shutdown") public HazelcastInstance hazelcast() {
  final Config c = hzConfig();
  final JoinConfig join = c.getNetworkConfig().getJoin();
  join.getMulticastConfig().setEnabled(false);
  join.getTcpIpConfig().setEnabled(true);
  return getOrCreateHazelcastInstance(c);
}

hzConfig() is a method where instance name, group name and password, map names, and map indices are configured, so I don't think it is of interest here.

hzConfig()是一个方法,在这里配置实例名、组名和密码、映射名称和映射索引,因此我认为这里不感兴趣。

And this is my Hibernate SessionFactory config:

这是我的Hibernate SessionFactory配置:

@Bean
public LocalSessionFactoryBean sessionFactory() {
  final LocalSessionFactoryBean b = new LocalSessionFactoryBean();
  b.setDataSource(dataSource);
  b.setHibernateProperties(props(
      "hibernate.connection.release_mode", "on_close",
      "hibernate.id.new_generator_mappings", "true",
      "hibernate.hbm2ddl.auto", "update",
      "hibernate.order_inserts", "true",
      "hibernate.order_updates", "true",
      "hibernate.max_fetch_depth", "0",
      "hibernate.jdbc.fetch_size", "200",
      "hibernate.jdbc.batch_size", "50",
      "hibernate.jdbc.batch_versioned_data", "true",
      "hibernate.jdbc.use_streams_for_binary", "true",
      "hibernate.use_sql_comments", "true"
  ));
  return b;
}

4 个解决方案

#1


7  

At some point, you mentioned that there was a NoClassDefFoundError for Logback. You got this fixed by removing this dependency, but then the problem moved to a another class - one of Spring's own classes.

在某些时候,您提到有一个NoClassDefFoundError用于回退。通过删除这个依赖项可以修复这个问题,但是问题转移到另一个类——Spring自己的一个类。

This can mean that either one of the libraries you have does something buggy with class loaders or maybe Tomcat needs instructions not to keep locks on some resources. See here more about Tomcat resources being locked and the <Context> setting to try: in your Tomcat's conf/context.xml place a antiResourceLocking="true" to the element.

这可能意味着,您拥有的其中一个库在类装入器上做了一些有问题的事情,或者Tomcat可能需要一些指令来避免对某些资源进行锁定。在这里可以看到更多关于被锁定的Tomcat资源和要尝试的 设置:在Tomcat的conf/ Context中。xml将一个antiResourceLocking=“true”放置到元素中。

#2


5  

Have you tried upping unloadDelay (defaults to 2000ms) for Tomcat contexts? See http://tomcat.apache.org/tomcat-7.0-doc/config/context.html

您是否尝试过为Tomcat上下文中升级unloadDelay(默认值为2000ms) ?见http://tomcat.apache.org/tomcat doc/config/context.html——7.0

UPDATE: I see that you are having issues with logback as well, it might be worth the shot to try and register this listener as well:

更新:我发现你也有登录问题,尝试注册这个监听器也是值得的:

class LogbackShutdownListener implements ServletContextListener {

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory();
        System.out.println("Shutting down Logback context '" + loggerContext.getName() + "' for " + contextRootFor(event));
        loggerContext.stop();
    }

    @Override
    public void contextInitialized(ServletContextEvent event) {
        System.out.println("Logback context shutdown listener registered for " + contextRootFor(event));
    }

    private String contextRootFor(ServletContextEvent event) {
        return event.getServletContext().getContextPath();
    }

}

}

Be sure to declare this listener before the spring context loader listener so that it is invoked after the context listener upon shutdown.

请确保在spring上下文装入器侦听器之前声明此侦听器,以便在关闭上下文侦听器之后调用该侦听器。

UPDATE 2: Also it might be worth the try to register another bean to handle closing of the Hazelcast stuff manually (be sure to also remove destroyMethod from the hazelcast bean):

更新2:同样值得尝试注册另一个bean来处理手工关闭Hazelcast的东西(确保也从Hazelcast bean中删除驱逐舰方法):

@Component
class HazelcastDestructor {

    @Autowired
    private HazelcastInstance instance;

    @PreDestroy
    public void shutdown() {
        try {
            instance.shutdown();
        } catch (Exception e) {
            System.out.println("Hazelcast failed to shutdown(): " + e);
            throw e;
        }
    }  
}

UPDATE 3: Just out of curiosity, have you tried parallel deployment: http://www.javacodegeeks.com/2011/06/zero-downtime-deployment-and-rollback.html. It might behave differently than reloading the very same context. At the very least you should be able to undeploy the old version lazily and see if that makes a difference.

更新3:出于好奇,您是否尝试过并行部署:http://www.javacodegeeks.com/2011/06/zero-downtime-deployment- rollback.html。它的行为可能不同于重载相同的上下文。至少,您应该能够延迟地取消旧版本的部署,看看这是否有什么不同。

#3


1  

There is a similar issue on the dangling threads while container restarting here.

当容器重新启动时,悬空线程也有类似的问题。

Of all the answers, one particular answer of interest was by Howard - which shows the way these threads are cleared.

在所有的答案中,霍华德给出了一个特别有趣的答案——这表明这些线程是如何被清除的。

There is some good discussion and reasoning as to how this can terminate the threads here.

这里有一些关于如何终止线程的很好的讨论和推理。

Now implement ServletContextListener and take care of these threads in the contextDestroyed() method as:

现在实现ServletContextListener并在contextdestroy()方法中处理这些线程,如下所示:

    public class YourListener implements ServletContextListener{
        ....
        @Override
        public void contextDestroyed(ServletContextEvent event) {
            //Call the immolate method here
        }
    }

Register this listener in WebApplicationInitilizer as:

在WebApplicationInitilizer中注册此侦听程序为:

     ctx.addListener(new YourListener());

So when server is restarted - contextDestroyed method is called and this takes care of all these threads.

因此,当服务器重新启动时——调用contextdestroy方法,这将处理所有这些线程。

#4


1  

From Web App development point of view, ServletContainer can only notify the before started and before end process of app. It is using ServletContextListener.

从Web App开发的角度来看,ServletContainer只能在App启动前和结束前通知,使用的是ServletContextListener。

Config ServletContextListener in web.xml

在web . xml配置ServletContextListener

<listener>
    <listener-class>com.var.YourListener</listener-class>
</listener>

YourListener.java

YourListener.java

public class YourListener implements ServletContextListener {

    public void contextInitialized(ServletContextEvent sce) {
        //initialization process
    }
    public void contextDestroyed(ServletContextEvent sce) {
        //destory process
    }
}   

Update -XML Less

更新通过少

Programmatically

以编程方式

@Override 
public void onStartup(ServletContext ctx) throws ServletException {

    ctx.addListener(new YourContextListener());

    ctx.setInitParameter("spring.profiles.active", "production");
    super.onStartup(ctx);
}   

Annotation

注释

@WebListener / @WebServletContextListener 
public class YourContextListener implements ServletContextListener {

    public void contextInitialized(ServletContextEvent servletContextEvent) {
    }

    public void contextDestroyed(ServletContextEvent servletContextEvent) {
    }
}

Update- ShoutDown Hook In Spring

更新-在春天的呼出钩

I never use it before my app development, we can register shoutdownhook event into AbstractApplicationContext of Spring. I am not sure it will be ok or not for you.

在我的应用开发之前我从未使用过它,我们可以将shoutdownhook事件注册到Spring的AbstractApplicationContext中。我不敢肯定对你是否合适。

AbstractApplicationContext context = ...
context.registerShutdownHook();

Reference 1 Reference 2

参考1参考2

#1


7  

At some point, you mentioned that there was a NoClassDefFoundError for Logback. You got this fixed by removing this dependency, but then the problem moved to a another class - one of Spring's own classes.

在某些时候,您提到有一个NoClassDefFoundError用于回退。通过删除这个依赖项可以修复这个问题,但是问题转移到另一个类——Spring自己的一个类。

This can mean that either one of the libraries you have does something buggy with class loaders or maybe Tomcat needs instructions not to keep locks on some resources. See here more about Tomcat resources being locked and the <Context> setting to try: in your Tomcat's conf/context.xml place a antiResourceLocking="true" to the element.

这可能意味着,您拥有的其中一个库在类装入器上做了一些有问题的事情,或者Tomcat可能需要一些指令来避免对某些资源进行锁定。在这里可以看到更多关于被锁定的Tomcat资源和要尝试的 设置:在Tomcat的conf/ Context中。xml将一个antiResourceLocking=“true”放置到元素中。

#2


5  

Have you tried upping unloadDelay (defaults to 2000ms) for Tomcat contexts? See http://tomcat.apache.org/tomcat-7.0-doc/config/context.html

您是否尝试过为Tomcat上下文中升级unloadDelay(默认值为2000ms) ?见http://tomcat.apache.org/tomcat doc/config/context.html——7.0

UPDATE: I see that you are having issues with logback as well, it might be worth the shot to try and register this listener as well:

更新:我发现你也有登录问题,尝试注册这个监听器也是值得的:

class LogbackShutdownListener implements ServletContextListener {

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        LoggerContext loggerContext = (LoggerContext)LoggerFactory.getILoggerFactory();
        System.out.println("Shutting down Logback context '" + loggerContext.getName() + "' for " + contextRootFor(event));
        loggerContext.stop();
    }

    @Override
    public void contextInitialized(ServletContextEvent event) {
        System.out.println("Logback context shutdown listener registered for " + contextRootFor(event));
    }

    private String contextRootFor(ServletContextEvent event) {
        return event.getServletContext().getContextPath();
    }

}

}

Be sure to declare this listener before the spring context loader listener so that it is invoked after the context listener upon shutdown.

请确保在spring上下文装入器侦听器之前声明此侦听器,以便在关闭上下文侦听器之后调用该侦听器。

UPDATE 2: Also it might be worth the try to register another bean to handle closing of the Hazelcast stuff manually (be sure to also remove destroyMethod from the hazelcast bean):

更新2:同样值得尝试注册另一个bean来处理手工关闭Hazelcast的东西(确保也从Hazelcast bean中删除驱逐舰方法):

@Component
class HazelcastDestructor {

    @Autowired
    private HazelcastInstance instance;

    @PreDestroy
    public void shutdown() {
        try {
            instance.shutdown();
        } catch (Exception e) {
            System.out.println("Hazelcast failed to shutdown(): " + e);
            throw e;
        }
    }  
}

UPDATE 3: Just out of curiosity, have you tried parallel deployment: http://www.javacodegeeks.com/2011/06/zero-downtime-deployment-and-rollback.html. It might behave differently than reloading the very same context. At the very least you should be able to undeploy the old version lazily and see if that makes a difference.

更新3:出于好奇,您是否尝试过并行部署:http://www.javacodegeeks.com/2011/06/zero-downtime-deployment- rollback.html。它的行为可能不同于重载相同的上下文。至少,您应该能够延迟地取消旧版本的部署,看看这是否有什么不同。

#3


1  

There is a similar issue on the dangling threads while container restarting here.

当容器重新启动时,悬空线程也有类似的问题。

Of all the answers, one particular answer of interest was by Howard - which shows the way these threads are cleared.

在所有的答案中,霍华德给出了一个特别有趣的答案——这表明这些线程是如何被清除的。

There is some good discussion and reasoning as to how this can terminate the threads here.

这里有一些关于如何终止线程的很好的讨论和推理。

Now implement ServletContextListener and take care of these threads in the contextDestroyed() method as:

现在实现ServletContextListener并在contextdestroy()方法中处理这些线程,如下所示:

    public class YourListener implements ServletContextListener{
        ....
        @Override
        public void contextDestroyed(ServletContextEvent event) {
            //Call the immolate method here
        }
    }

Register this listener in WebApplicationInitilizer as:

在WebApplicationInitilizer中注册此侦听程序为:

     ctx.addListener(new YourListener());

So when server is restarted - contextDestroyed method is called and this takes care of all these threads.

因此,当服务器重新启动时——调用contextdestroy方法,这将处理所有这些线程。

#4


1  

From Web App development point of view, ServletContainer can only notify the before started and before end process of app. It is using ServletContextListener.

从Web App开发的角度来看,ServletContainer只能在App启动前和结束前通知,使用的是ServletContextListener。

Config ServletContextListener in web.xml

在web . xml配置ServletContextListener

<listener>
    <listener-class>com.var.YourListener</listener-class>
</listener>

YourListener.java

YourListener.java

public class YourListener implements ServletContextListener {

    public void contextInitialized(ServletContextEvent sce) {
        //initialization process
    }
    public void contextDestroyed(ServletContextEvent sce) {
        //destory process
    }
}   

Update -XML Less

更新通过少

Programmatically

以编程方式

@Override 
public void onStartup(ServletContext ctx) throws ServletException {

    ctx.addListener(new YourContextListener());

    ctx.setInitParameter("spring.profiles.active", "production");
    super.onStartup(ctx);
}   

Annotation

注释

@WebListener / @WebServletContextListener 
public class YourContextListener implements ServletContextListener {

    public void contextInitialized(ServletContextEvent servletContextEvent) {
    }

    public void contextDestroyed(ServletContextEvent servletContextEvent) {
    }
}

Update- ShoutDown Hook In Spring

更新-在春天的呼出钩

I never use it before my app development, we can register shoutdownhook event into AbstractApplicationContext of Spring. I am not sure it will be ok or not for you.

在我的应用开发之前我从未使用过它,我们可以将shoutdownhook事件注册到Spring的AbstractApplicationContext中。我不敢肯定对你是否合适。

AbstractApplicationContext context = ...
context.registerShutdownHook();

Reference 1 Reference 2

参考1参考2