Spring Boot 1.5.9.RELEASE + Java 8 + Tomcat 9 + Jersey + Oracleを使用しており、アプリには次のように定義されたメソッドがスケジュールされています。
@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
@Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(100);
}
}
職種:
@Component
public class ClearCacheJob {
@Scheduled(fixedRate = 3600000, initialDelay = 10000)
public void clearErrorCodesCache() {
try {
logger.info("######## ClearCacheJob #########");
} catch (Exception e) {
logger.error("Exception in ClearCacheJob", e);
}
}
}
また、次のようにOracleドライバーを登録解除するクラスがあります。
@WebListener
public class ContainerContextClosedHandler implements ServletContextListener {
private static final Logger logger = LoggerFactory.getLogger(ContainerContextClosedHandler.class);
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
logger.info("######### contextInitialized #########");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
logger.info("######### contextDestroyed #########");
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
try {
DriverManager.deregisterDriver(driver);
logger.info(String.format("deregistering jdbc driver: %s", driver));
} catch (SQLException e) {
logger.info(String.format("Error deregistering driver %s", driver), e);
}
}
}
}
しかし、Tomcatを停止すると、次のエラーが表示されます。
WARNING [Thread-11] org.Apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [hai]
appears to have started a thread named [Timer-0] but has failed to stop it.
This is very likely to create a memory leak. Stack trace of thread:
Java.lang.Object.wait(Native Method)
Java.lang.Object.wait(Unknown Source)
Java.util.TimerThread.mainLoop(Unknown Source)
Java.util.TimerThread.run(Unknown Source)
なぜこのエラーが発生し、どうすれば修正できますか?
ScheduleConfig
の代わりにshutdownNow
をdestroyメソッドとして使用するようにshutdown
を変更します。
@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
@Bean(destroyMethod = "shutdownNow")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(100);
}
}
この問題の根本原因分析といくつかのソリューションを共有したいと思います。
Oracleユーザーの場合:
ソリューション#1:
Oracleドライバーを/WEB-INF/lib
フォルダーから削除して、Tomcatの/lib
フォルダーに配置できます。問題が解決する場合があります。
ソリューション#2:
スレッドをスリープさせることで、実際のハックを使用できます。
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
logger.info("######### contextDestroyed #########");
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
try {
DriverManager.deregisterDriver(driver);
logger.info(String.format("deregistering jdbc driver: %s", driver));
} catch (SQLException e) {
logger.info(String.format("Error deregistering driver %s", driver), e);
}
}
try { Thread.sleep(2000L); } catch (Exception e) {} // Use this thread sleep
}
リソースリンク:「Tomcatが[放棄された接続クリーンアップスレッド]」を停止できないという解決策
ソリューション#3:
Svetlin Zarev は心配することを何も告げていません。 Tomcatの標準メッセージです。彼は以下のような根本原因分析を与えました:
この問題は、アプリケーションがScheduledExecutorを開始したときに発生します(ただし、これは他のThread/TheadPoolで発生します)が、contextDestroyedでシャットダウンしませんでした。そのため、アプリケーション/サーバーの停止時にスレッドをシャットダウンしているかどうかを確認してください。
リソースリンク:Tomcat8メモリリーク
ソリューション#4:
Oracleユーザーの場合、この投稿には複数の回答があります: メモリリークを防ぐため、JDBCドライバーは強制的に登録解除されました
ソリューション#5:
ソリューションによる根本原因分析:
NonRegisteringDriverクラスの放棄された接続のクリーンアップスレッドは、静的シャットダウンメソッドを持つようにリファクタリングされました。メモリは割り当てられましたが、解放されませんでした。このリークの問題が発生した場合は、
contextDestroyed
メソッドのAbandonedConnectionCleanupThread.shutdown()
呼び出しでアプリケーションにコンテキストリスナーを実装してください。この問題は、Tomcatアプリケーションサーバーで実行されているアプリケーションで見つかりましたが、他のアプリケーションサーバーにも適用されている可能性があります。
例えば:
@WebListener public class YourThreadsListener implements ServletContextListener { public void contextDestroyed(ServletContextEvent arg0) { try { AbandonedConnectionCleanupThread.shutdown(); } catch (InterruptedException e) { } } ... }
コンテナが注釈をサポートしていない場合、web.xmlに説明を追加することに注意してください。
<listener> <listener-class>user.package.YourThreadsListener</listener-class> </listener>
リソースリンク:https://docs.Oracle.com/cd/E17952_01/connector-j-relnotes-en/news-5 -1-23.html
コードに基づいていくつかのテストを実行し、オンラインで調査した後の私の結論:
心配する必要はありません( link )。 Tomcatプロセスは終了しており、メモリリークは残っていません。
AbandonedConnectionCleanupThread.shutdown()
のようなものを呼び出しても、同じ警告が表示される可能性があります( link )
この警告は、startup.sh
およびshutdown.sh
を呼び出すときに発生します。 EclipseからTomcatを実行する場合、その警告は表示されません。
Executor
のシャットダウンメソッドが呼び出されている可能性があります。私のテストでは、エグゼキュータのdestroyMethod
を定義していなくても呼び出されていました。
この場合、この警告はSpring Scheduling Beanとは関係ありません。 Executors.newScheduledThreadPool
は、新しいScheduledThreadPoolExecutor
を返します。これにはdestroyメソッドがあり、前に指摘したように破棄されます。デバッグして自分で確認できます。
ただし、new Java.util.Timer
を呼び出すコードのどこかにnew TimerThread()
を呼び出します。ログからわかるように、@ Claudio Corsiが指摘したとおりです。
それをデバッグするために、Eclipseを使用している場合、JDKバージョンのソースコードを添付する必要があります。クラス宣言を開き(ctrlを押して宣言を開くを選択)、「ソースコードを添付」ボタンをクリックします。正確に同じバージョンをダウンロードしたことを確認してください。 Zipを抽出する必要さえありません。 Mavenを使用している場合は、Mavenが自動的にダウンロードすることを少し待ってください。
次に、Java.util.Timer
のコンストラクターにブレークポイントを配置し、アプリケーションのデバッグを開始します。
Edit:Java.util.Timer
への参照を特定した後、それを保存し(Beanでない場合はBeanとして)、コンテキストでそのcancel
メソッドを呼び出します破壊する。
根本原因を特定することは困難ですが、スレッド名[Timer-0]はそれを見つける手がかりを与えます。 Java.util.Timer
クラスは、ソースコードでわかるように、Timer-*のような名前パターンを持つスレッドを作成します。
public Timer() {
this("Timer-" + serialNumber());
}
おそらく、クラスパスにあるライブラリがTimerスレッドを開始しますが、キャンセルしないか、このスレッドで動作しているコードがスタックします。
Java.util.Timer
にブレークポイントを置き、デバッグして、どのタスクがそれに取り組んでいるかを見つけることをお勧めします。根本原因を示している可能性があります。
私も次のエラーで同じ問題を抱えています:
The web application [ROOT] appears to have started a thread named [cluster-ClusterId{value='5d29b78967e4ce07d9bb8705', description='null'}-localhost:27017] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
だから、しばらくして、私は自分のスプリングブートアプリケーションのすべてのサブモジュールに対してMavenのインストールを行わなかったことがわかりました。したがって、同じエラーが発生しているかどうかを再確認してください。
mvn clean install -U
を実行しました。