Tomcat再起動時に [Abandoned connection cleanup thread] がメモリリークしてるという警告が出る場合の対応

Tomcat上のWebアプリケーションでMySQLJDBCドライバを使用すると、Tomcat再起動時に以下のような警告が出力されます。

The web application [] appears to have started a thread named [Abandoned connection cleanup thread] but has failed to stop it. This is very likely to create a memory leak.

これはMySQLにバグチケットが上がっていて、対処方法もそちらに記載があります。

MySQL Bugs: #65909: referenceThread causes memory leak in Tomcat

ServletContextListener#contextDestroyed() で、該当のスレッドを止めてやれば良い訳ですね。こんな感じです。

@WebListener
public class StopLeakThreadListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // do nothing
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        try {
            AbandonedConnectionCleanupThread.shutdown();
        } catch (final InterruptedException e) {
        }
    }
}

これをやるときは、mysql-connector-java が compile スコープで必要になります。通常JDBCドライバは runtime にしていると思いますのでご注意を。

(おまけ)JDBCドライバも登録解除する

Tomcat再起動時、下記のようなログもでることがあります。

SEVERE: A web application registered the JBDC driver [com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.

こちらはTomcatのメモリリーク防止機能による「JDBCドライバが残ってたから登録解除しといたよ!」というメッセージのため、無視しても問題ありません。 ただ、SEVEREでログが出ているので、何となく気持ち悪かったり、「本当に大丈夫ですか、検証しましたか、だれが責任取るんですか」みたいなことを言われる可能性が多少でもあったりする場合は、明示的にドライバを登録解除することで精神の安定を得ることができます。

上の #contextDestroyed() メソッドに以下のようなコードを追加します。

Collections.list(DriverManager.getDrivers()).forEach(driver -> {
    try {
        DriverManager.deregisterDriver(driver);
    } catch (final Exception e) {}
});

DriverManager.getDrivers()Enumeration<Driver> を返すのですが、for文は禁止 です。