Tomcat7.0.70にデプロイされているWebApplicationがあります。次の状況をシミュレートしました。
[〜#〜]結果[〜#〜]
スレッドダンプ
次の画面で、「再デプロイ」をクリックした後、スレッド「http-apr-8081-exec-10」を除くすべてのスレッド(このWebアプリケーションに関連付けられていた)が強制終了されたことがわかります。 Tomcatの属性 "renewThreadsWhenStoppingContext == true"を設定すると、しばらくするとこのスレッド( "http-apr-8081-exec-10")が強制終了され、新しいスレッド(http-apr-8081-exec-11)が表示されます。 )の代わりに作成されました。したがって、ヒープダンプ3の作成後、古いスレッドやオブジェクトがないため、古いWCLが存在するとは思っていませんでした。
ヒープダンプ1
次の2つの画面では、アプリケーションの実行時にWCLが1つしかなかったことがわかります(そのパラメーター "started" = true)。また、スレッド「http-apr-8081-exec-10」には、contextClassLoader = URLClassLoaderがありました(Tomcatのプールにあったため)。このスレッドが私の将来のHTTPリクエストを処理することがわかるので、私はこのスレッドについてのみ話します。
HTTPリクエストの送信
これでHTTPリクエストを送信し、コードで現在のスレッドに関する情報を取得します。リクエストがスレッド「http-apr-8081-exec-10」によって処理されていることがわかります。
дек 23, 2016 9:28:16 AM c.c.c.f.s.r.ReportGenerationServiceImpl INFO: request has been handled in
thread = http-apr-8081-exec-10, its contextClassLoader = WebappClassLoader
context: /hdi
delegate: false
repositories:
/WEB-INF/classes/
----------> Parent Classloader: Java.net.URLClassLoader@4162ca06
次に、[Webアプリケーションを再デプロイする]をクリックすると、コンソールに次のメッセージが表示されます。
дек 23, 2016 9:28:27 AM org.Apache.catalina.loader.WebappClassLoaderBase clearReferencesThreads
SEVERE: The web application [/hdi] appears to have started a thread named [http-apr-8081-exec-10] but has failed to stop it. This is very likely to create a memory leak.
ヒープダンプ2
次の画面では、WebAppClassLoaderのインスタンスが2つあることがわかります。それらの1つ(番号#1)は古いです(その属性 "started" = false)。また、WCL#2は、アプリケーションを再デプロイした後に作成されました(その属性 "started" = true)。また、レビューするスレッドにはcontextClassLoader = "org.Apache.catalina.loader.WebappClassLoader"があります。どうして? contextClassLoader = "Java.net.URLClassLoader"が表示されることを期待していました(結局のところ、スレッドが作業を終了すると、Tomcatのプールに返され、その属性 "contextClassLoader"は任意の基本クラスローダーに設定されます)。
ヒープダンプ3
スレッド「http-apr-8081-exec-10」はありませんが、スレッド「http-apr-8081-exec-11」があり、contextClassLoader = "WebappClassLoader"があります(URLClassLoaderを使用しないのはなぜですか?) 。
最終的には次のようになります。WebappClassLoader#1への参照を持つスレッド「http-apr-8081-exec-11」があります。そして明らかに、WCL#1で「NearestGC Root」を作成すると、スレッド11への参照が表示されます。
質問。
スレッドが動作を終了した後、Tomcatに古い値contextClassLoader(URLClassLoader)を返すように強制的に指示するにはどうすればよいですか?
スレッドの更新中にTomcatが古い値「contextClassLoader」をコピーしないようにするにはどうすればよいですか?
たぶん、私の問題を解決する他の方法を知っていますか?
Tomcatは通常、実稼働環境では適切なオプションではありません。いくつかの本番アプリケーションでTomcatを使用していましたが、ヒープサイズやその他の構成が適切に設定されていても、アプリケーションをリロードするたびに、メモリ消費量が増加することがわかりました。 Tomcatサービスを再起動しない限り、メモリは完全に再利用されません。ログのクリア、すべてのアプリの再デプロイ、月に1回または週に1回、最も忙しくない時間帯に定期的にTomcatを再起動するなど、このようなすべての実験をテストしました。しかし、最後に、本番環境をGlassfishとWebSphereに移行したと言わざるを得ません。
私はあなたがすでにこれらのページを通過したことを願っています:
https://developers.redhat.com/blog/2014/08/14/find-fix-memory-leaks-Java-application/
http://www.tomcatexpert.com/blog/2010/04/06/tomcats-new-memory-leak-prevention-and-detection
WebアプリケーションがTomcatと緊密に結合されていない場合は、別のWebコンテナを使用することを検討できます。現在、Glassfishは開発マシンや生産でも使用されており、この決定を下した日には、多くの時間を節約できました。 Glassfishやその他のサーバーは、Tomcatほど軽量ではないため、起動に時間がかかりますが、後の作業は少し簡単です。
この問題に関する私の経験から、Tomcatが古いクラスローダーを適切にGCするのを妨げていたのは、私が使用していたいくつかのフレームワークが作成していた(そして適切に処理しなかった)いくつかのThreadLocal
sでした。
ここで説明されているものに似たもの: ThreadLocal&Memory Leak
私はこのThreadLocal
sを適切にファイナライズしようとしましたが、リークによってALOTが減少しました。それでもリークはありましたが、以前の10倍の再デプロイを処理できました。
どういうわけかThreadLocal
sに接続できるオブジェクトへのメモリダンプを確実にチェックします(特に、トランザクションを制御するために何かを使用する場合、またはスレッドで分離されているものを使用する場合は、非常に一般的です)。
お役に立てば幸いです。
Tomcatは通常、実稼働環境では適切なオプションではありません。いくつかの本番アプリケーションでTomcatを使用していましたが、ヒープサイズやその他の構成が適切に設定されていても、アプリケーションをリロードするたびに、メモリ消費量が増加することがわかりました。 Tomcatサービスを再起動しない限り、メモリは完全に再利用されません。ログのクリア、すべてのアプリの再デプロイ、月に1回または週に1回、最も忙しくない時間帯に定期的にTomcatを再起動するなど、このようなすべての実験をテストしました。しかし、最後に、本番環境をGlassfishとWebSphereに移行したと言わざるを得ません。
Tomcatの再展開でのメモリリークは非常に古い問題です。これを解決する唯一の実際の方法は、アプリケーションを再デプロイする代わりにTomcatを再起動することです。複数のアプリがある場合は、異なるポートで複数のTomcatのサービスを実行し、nginxに参加させる必要があります。
ClassLoaderがガベージコレクションされるのを防ぐThreadLocalの使用を確認してください。 ThreadLocal値でクラスへの参照を削除するか、ThreadLocalの代わりに https://github.com/codesinthedark/ImprovedThreadLocal を使用してください
数百のTomcatインスタンスがいくつかの環境(本番環境でも)で実行されており、この問題に対して私たちが見つけた唯一の合理的な解決策は、設定された時間にすべてのTomcatを停止して再起動することです毎日(夜間) 。
私たちは多くのトリックを試しましたが、これは稼働時間の要件に対する永続的なソリューションです。