今天遇到一个非常奇怪的问题,如标题所看到的:
linux下(之所以强调linux下,是由于在windows下正常),运行tomcat ./shutdown.sh 后,尽管tomcat服务不能正常訪问了,可是ps -ef | grep java 后,发现tomcat相应的java进程未随web容器关闭而销毁,进而存在僵尸java进程。
刚開始百思不得其解,google下,发现存在一种说法是:有非守护线程(即User Thread)存在,jvm不会退出(当JVM中全部的线程都是守护线程的时候,JVM就能够退出了;假设另一个或以上的非守护线程则JVM不会退出)。使用jstack工具进而确认是由于项目代码存在
scheduledExecutorService.scheduleAtFixedRate,众所周知,executorService会为之维护一个定时服务的线程池,该线程池并不会随着web容器关闭而销毁,只是有点让我非常费解,虽然我已经在代码中addShutdownHook(shutdown()) ,但实际证明,这个hook仅仅对Application.main时有效,在web容器中,hook会失效,所以并不会由于web容器关闭触发hook,进而能shutdown scheduledExecutorService。
(线程则是JVM级别的,假设你在Web 应用中启动一个线程,这个线程的生命周期并不会和Web应用程序保持同步。也就是说,即使你停止了Web应用,这个线程依然是活跃的。正是由于这个非常隐晦的问题,所以非常多有经验的开发人员不太赞成在Web应用中私自启动线程。
假设我们手工使用JDK Timer(Quartz的Scheduler),在Web容器启动时启动Timer,当Web容器关闭时,除非你手工关闭这个Timer,否则Timer中的任务还会继续执行!
Spring为JDK Timer和Quartz Scheduler所提供的TimerFactoryBean和SchedulerFactoryBean可以和Spring容器的生命周期关联,在 Spring容器启动时启动调度器,而在Spring容器关闭时,停止调度器。所以在Spring中通过这两个FactoryBean配置调度器,再从 Spring IoC中获取调度器引用进行任务调度将不会出现这样的Web容器关闭而任务依旧执行的问题。而假设你在程序中直接使用Timer或Scheduler,如不进行额外的处理,将会出现这一问题。
)
知道原因后,处理事实上非常easy,在ContextLoaderListener#contextDestroyed 容器关闭时主动销毁scheduledExecutorService:
public void contextDestroyed(ServletContextEvent event) { //close ScheduledExecutorService in web container AccessTokenScheduled.shutdownScheduledExecutor(); SystemTimer.shutdownScheduledExecutor(); super.contextDestroyed(event); log.info("AppListener # context destroyed... "); }
參考:http://blog.sina.com.cn/s/blog_613904cc0101i5em.html
http://m.blog.csdn.net/blog/lin910429/25716113
http://www.2cto.com/kf/201305/214127.html