web-dev-qa-db-ja.com

Tomcatのシャットダウン時にスケジュールされたタイマーを停止する

TomcatサーバーにWARファイルをデプロイしました。クラスの1つが起動時に呼び出され、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]という名前のスレッドを開始したようですが、停止できませんでした。これはメモリリークを引き起こす可能性が非常に高いです。

これは私がタイマーをスケジュールしたことが原因であると理解していますが、私の質問は次のとおりです。

  1. 私はsetDeamonをtrueに設定しなかったので、タイマーはTomcatが実行されたままになるのではなく、シャットダウンすることを妨げるべきではありませんか?
  2. アプリケーションで、Tomcatがシャットダウンされることを検出して、タイマーをキャンセルできますか?
  3. この問題を解決するために使用できる他のソリューションは何ですか?

ありがとう!

[〜#〜]更新[〜#〜]

いくつかの検索と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) {

}
_

今私の新しい質問:

  1. 最初にTimerTaskをキャンセルし、次にTimerをキャンセルします。これは正しいですか?
  2. 他にすべきことはありますか?

ありがとう!

[〜#〜]更新[〜#〜]

動作しません。いくつかのロギングステートメントをcontextDestroyed()メソッドに挿入しました。Tomcatをシャットダウンした後、ログファイルには以下のみが含まれています。

PowderGodAppWebService-> [2012年2月7日04:09:46 PM] INFO(PowderGodAppWebService.Java:45):: Tomcatサーバーを検出IS GOING TO SHUT DOWNシャットダウンPowderGodAppWebService-> [2012年2月7日04: 09:46 PM] INFO(PowderGodAppWebService.Java:46)::タイマータスクとタイマーのキャンセル

CANCELING COMPLETEはありません。

実行中のプロセスも確認しました(私はLinuxの専門家ではないので、Macのアクティビティモニターを使用しています)。

  • Javaプロセスが実行されていないことを確認してください
  • Tomcatを起動し、そのPIDをメモしますJavaプロセス
  • Tomcatを停止する
  • Tomcatプロセスがなくなっていることがわかりました
  • Tomcatを起動し、そのPIDをメモしますJavaプロセス
  • Warファイルをデプロイする
  • プロセスをサンプリングします。[Timer-0]スレッドが存在することを確認してください
  • Tomcatのシャットダウン
  • プロセスはまだそこにあることがわかりました
  • プロセスをサンプリングする
  • [Timer-0]がまだあるのを見てください

[〜#〜]固定[〜#〜]

Tomcatが実際にシャットダウンした後にparserTimer = new Timer(true);が呼び出されるため、タイマーがデーモンスレッドとして実行されるように、コードをcontextDestroyed()に変更しました。

「すべてのサーブレットとフィルタは、ServletContextListenersにコンテキストの破棄が通知される前に破棄されます。」

http://docs.Oracle.com/javaee/6/api/javax/servlet/ServletContextListener.html

22
Derek Li

Java EE環境でTimerを使用するしないを実行してください!タスクがランタイム例外をスローした場合、その後、Timer全体が強制終了され、実行されなくなります。基本的に、サーバー全体を再起動して再度実行する必要があります。また、システムクロックの変化に敏感です。

代わりに ScheduledExecutorService を使用してください。タスクでスローされた例外やシステムクロックの変化には影響されません。 shutdownNow()メソッドでシャットダウンできます。

ServletContextListener実装全体の例を次に示します(注:新しいweb.xmlアノテーションにより、@WebListenerへの登録は不要です):

@WebListener
public class BackgroundJobManager implements ServletContextListener {

    private ScheduledExecutorService scheduler;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(new YourParsingJob(), 0, 5, TimeUnit.HOUR);
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        scheduler.shutdownNow();
    }

}
65
BalusC

サーブレットがアンロードされる直前に、サーブレットのdestroyメソッドが呼び出されます。タイマーをインスタンス変数にするためにparserTimer自体のスコープを変更した場合、そこからタイマーをキャンセルできます。 initとdestroy内からのみアクセスできれば、問題は発生しません。

サーブレットエンジンは、サーブレットが適切であると判断した場合はいつでも自由にアンロードできますが、実際には、サーブレットエンジンが停止したときに呼び出されることはありません。

1
DaveH