关闭 tomcat 时停止计划计时器
我有一个部署到 Tomcat 服务器的 WAR 文件,其中一个类将在启动时被调用,然后 init() 方法将安排一个计时器每 5 小时触发一次以执行一些任务。
我的 init() 代码如下所示:
public void init()
{
TimerTask parserTimerTask = new TimerTask() {
@Override
public void run() {
XmlParser.parsePage();
}
};
Timer parserTimer = new Timer();
parserTimer.scheduleAtFixedRate(parserTimerTask, 0, PERIOD);
}
我的应用程序运行没有问题,但是当我使用 /etc/init.d/tomcat7 stop 关闭 Tomcat 时,我检查日志 (catalina.out)有这样的条目:
严重:Web 应用程序 [/MyApplication] 似乎已启动名为 [Timer-0] 的线程,但无法停止它。这很可能会造成内存泄漏。
我知道这是由我安排计时器引起的,但我的问题是:
- 我没有将
setDeamon
设置为 true,所以不应该计时器阻止 Tomcat 关闭,而不是继续运行? - 我可以在我的应用程序中检测到 Tomcat 将要关闭并取消我的计时器吗?
- 我可以使用哪些其他解决方案来解决此问题?
谢谢!
更新
根据一些搜索和 DaveHowes 的回答,我将代码更改为以下内容。
Timer parserTimer;
TimerTask parserTimerTask;
public void init()
{
parserTimerTask = new TimerTask() {
@Override
public void run() {
XmlParser.parsePage();
}
};
parserTimer = new Timer();
parserTimer.scheduleAtFixedRate(parserTimerTask, 0, PERIOD);
}
@Override
public void contextDestroyed(ServletContextEvent arg0) {
Logger logger = Logger.getRootLogger();
logger.info("DETECT TOMCAT SERVER IS GOING TO SHUT DOWN");
logger.info("CANCEL TIMER TASK AND TIMER");
otsParserTimerTask.cancel();
otsParserTimer.cancel();
logger.info("CANCELING COMPLETE");
}
@Override
public void contextInitialized(ServletContextEvent arg0) {
}
现在我的新问题:
- 我先取消 TimerTask,然后取消 Timer,这是正确的吗?
- 我还应该做其他事情吗?
谢谢!
更新
它不起作用。我在 contextDestroyed() 方法中放置了一些日志记录语句,关闭 Tomcat 后,日志文件只有以下内容:
PowderGodAppWebService -> [2012 年 2 月 7 日 04:09:46 PM] 信息 (PowderGodAppWebService.java:45):: 检测 TOMCAT 服务器即将关闭 PowderGodAppWebService -> [2012 年 2 月 7 日 04:09:46 PM] 信息 (PowderGodAppWebService.java:46):: 取消计时器任务和计时器
取消完成不存在。
我还检查了正在运行的进程(我不是 Linux 专家,所以我只使用 Mac 的活动监视器。
- 确保没有 java 进程正在运行
- 启动 Tomcat,记下该 java 进程的 PID
- 停止 Tomcat
- 发现 Tomcat 进程已消失
- 启动 Tomcat ,记下该 java 进程的 PID
- 部署我的 war 文件
- 采样进程,查看 [Timer-0] 线程是否存在
- 关闭 Tomcat
- 发现该进程仍然存在
- 采样进程
- 查看 [Timer-0] 是否仍然存在
已修复
我将代码更改为parserTimer = new Timer(true);
,以便我的计时器作为守护线程运行,因为contextDestroyed()
得到在 Tomcat 实际关闭后调用。
销毁之前,所有 servlet 和过滤器都将被销毁。”
“在通知任何 ServletContextListener上下文 href="http://docs.oracle.com/javaee/6/api/javax/servlet/ServletContextListener.html">http://docs.oracle.com/javaee/6/api/javax/servlet/ServletContextListener。 html
I have a WAR file deployed to Tomcat server, one of the class will get called at start up time, then the init() method will schedule a timer to fire every 5 hours to perform some tasks.
My init() code looks like this:
public void init()
{
TimerTask parserTimerTask = new TimerTask() {
@Override
public void run() {
XmlParser.parsePage();
}
};
Timer parserTimer = new Timer();
parserTimer.scheduleAtFixedRate(parserTimerTask, 0, PERIOD);
}
My application runs without problem, but when I shutdown the Tomcat using /etc/init.d/tomcat7 stop, then I check the log (catalina.out) it has a entry like this:
SEVERE: The web application [/MyApplication] appears to have started a thread named [Timer-0] but has failed to stop it. This is very likely to create a memory leak.
I understand this is caused by me schedule the timer, but my question is:
- I didn't set
setDeamon
to true, so shouldn't the timer prevent Tomcat from shutting down, rather than left running? - Can I, in my application, detect Tomcat is going to be shutdown and cancel my timer?
- What are the other solutions I can use to take care of this issue?
Thanks!
UPDATE
I changed my code to the following based on some search and DaveHowes's answer.
Timer parserTimer;
TimerTask parserTimerTask;
public void init()
{
parserTimerTask = new TimerTask() {
@Override
public void run() {
XmlParser.parsePage();
}
};
parserTimer = new Timer();
parserTimer.scheduleAtFixedRate(parserTimerTask, 0, PERIOD);
}
@Override
public void contextDestroyed(ServletContextEvent arg0) {
Logger logger = Logger.getRootLogger();
logger.info("DETECT TOMCAT SERVER IS GOING TO SHUT DOWN");
logger.info("CANCEL TIMER TASK AND TIMER");
otsParserTimerTask.cancel();
otsParserTimer.cancel();
logger.info("CANCELING COMPLETE");
}
@Override
public void contextInitialized(ServletContextEvent arg0) {
}
Now my new question:
- I cancel TimerTask first then Timer, is this correct?
- Are there other thing I should do?
Thanks!
UPDATE
It doesn't work. I put some logging statement in the contextDestroyed() method, after I shutdown Tomcat, the log file only has the following:
PowderGodAppWebService -> [07 Feb 2012 04:09:46 PM] INFO (PowderGodAppWebService.java:45):: DETECT TOMCAT SERVER IS GOING TO SHUT DOWN
PowderGodAppWebService -> [07 Feb 2012 04:09:46 PM] INFO (PowderGodAppWebService.java:46):: CANCEL TIMER TASK AND TIMER
CANCELING COMPLETE is not there.
I also checked processes that are running (I'm not a Linux expert so I just use Mac's Activity Monitor.
- Make sure no java process is running
- Start Tomcat, note the PID of that java process
- Stop Tomcat
- Found the Tomcat process is gone
- Start Tomcat, note the PID of that java process
- Deploy my war file
- Sample the process, see [Timer-0] thread is there
- Shutdown Tomcat
- Found that the process is still there
- Sample the process
- See [Timer-0] is still there
FIXED
I changed my code to parserTimer = new Timer(true);
so that my timer runs as a daemon thread because the contextDestroyed()
gets called after Tomcat actually shuts down.
"All servlets and filters will have been destroyed before any ServletContextListeners are notified of context destruction."
http://docs.oracle.com/javaee/6/api/javax/servlet/ServletContextListener.html
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
请勿在 Java EE 环境中不要使用
Timer
!如果任务抛出运行时异常,则整个 Timer 将被终止并且不再运行。您基本上需要重新启动整个服务器才能使其再次运行。此外,它对系统时钟的变化也很敏感。使用
ScheduledExecutorService
相反。它对任务中抛出的异常和系统时钟的变化不敏感。您可以通过其shutdownNow()
方法关闭它。下面是整个
ServletContextListener
实现的示例(注意:由于新的@WebListener
注释,无需在web.xml
中注册):Do not use
Timer
in an Java EE environment! If the task throws a runtime exception, then the wholeTimer
is killed and won't run anymore. You basically needs to restart the whole server to get it to run again. Also, it is sensitive to changes in the system clock.Use
ScheduledExecutorService
instead. It's not sensitive to exceptions thrown in tasks nor to changes in system clock. You can shutdown it by itsshutdownNow()
method.Here's an example of how the entire
ServletContextListener
implementation can look like (note: no registration inweb.xml
required thanks to the new@WebListener
annotation):当 servlet 即将被卸载时,将调用 servlet destroy 方法。您可以从那里取消计时器,前提是您更改了 parserTimer 本身的范围以使其成为实例变量。如果您仅从 init 和 destroy 内部访问它,我认为这没有问题。
servlet 引擎可以在认为合适的时候自由地卸载 servlet,但实际上,我只在 servlet 引擎停止时才看到它被调用 - 其他人可能有其他的经验,尽管这可能证明我是错的。
The servlets destroy method is called as the servlet is about to be unloaded. You could cancel the timer from within there, providing that you altered the scope of the parserTimer itself to make it an instance variable. I don't see a problem with that provided that you access it only from within init and destroy.
The servlet engine is free to unload the servlet whenever it sees fit, but in pratice I'm only ever seen it called as the servlet engine is stopped - other people may have other experiences of this though that might prove me wrong.
尝试使用框架进行调度。
如果您采用 Spring 框架,您可以使用内置的调度功能。
当使用 Spring 进行调度时,停止应用程序服务器从来没有遇到任何问题。
Try to use framework for scheduling.
If you adapt Spring Framework you could use the build in scheduling capabilities.
When scheduling with Spring I had never any problem stopping application server.
将 parseTimer.purge() 放入 onContetexyDestroyed 中。它将删除队列中的所有计时器(如果存在)。
Put parseTimer.purge() in your onContetexyDestroyed.It will remove all the timers in the queue if they exist.