web-dev-qa-db-ja.com

Tomcatでアプリケーションを再デプロイするときのメモリリーク

Tomcat7.0.70にデプロイされているWebApplicationがあります。次の状況をシミュレートしました。

  1. ヒープダンプを作成しました。
  2. 次に、Httpリクエストを送信し、サービスのメソッドで現在のスレッドとそのclassLoaderを出力しました。そして、Thread.currentThread.sleep(10000)を呼び出しました。
  3. 同時に、Tomcatの管理ページで「このアプリケーションのアンデプロイ」をクリックしました。
  4. 新しいヒープダンプを作成しました。
  5. 数分後、新しいhepダンプを作成しました。


[〜#〜]結果[〜#〜]


スレッドダンプ

次の画面で、「再デプロイ」をクリックした後、スレッド「http-apr-8081-exec-10」を除くすべてのスレッド(このWebアプリケーションに関連付けられていた)が強制終了されたことがわかります。 Tomcatの属性 "renewThreadsWhenStoppingContext == true"を設定すると、しばらくするとこのスレッド( "http-apr-8081-exec-10")が強制終了され、新しいスレッド(http-apr-8081-exec-11)が表示されます。 )の代わりに作成されました。したがって、ヒープダンプ3の作成後、古いスレッドやオブジェクトがないため、古いWCLが存在するとは思っていませんでした。

enter image description here

ヒープダンプ1

次の2つの画面では、アプリケーションの実行時にWCLが1つしかなかったことがわかります(そのパラメーター "started" = true)。また、スレッド「http-apr-8081-exec-10」には、contextClassLoader = URLClassLoaderがありました(Tomcatのプールにあったため)。このスレッドが私の将来のHTTPリクエストを処理することがわかるので、私はこのスレッドについてのみ話します。

enter image description here

enter image description here

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"は任意の基本クラスローダーに設定されます)。

enter image description here

enter image description here

enter image description here

ヒープダンプ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への参照が表示されます。

enter image description here

enter image description here

質問。

スレッドが動作を終了した後、Tomcatに古い値contextClassLoader(URLClassLoader)を返すように強制的に指示するにはどうすればよいですか?

スレッドの更新中にTomcatが古い値「contextClassLoader」をコピーしないようにするにはどうすればよいですか?

たぶん、私の問題を解決する他の方法を知っていますか?

22
Konstantin B.

Tomcatは通常、実稼働環境では適切なオプションではありません。いくつかの本番アプリケーションでTomcatを使用していましたが、ヒープサイズやその他の構成が適切に設定されていても、アプリケーションをリロードするたびに、メモリ消費量が増加することがわかりました。 Tomcatサービスを再起動しない限り、メモリは完全に再利用されません。ログのクリア、すべてのアプリの再デプロイ、月に1回または週に1回、最も忙しくない時間帯に定期的にTomcatを再起動するなど、このようなすべての実験をテストしました。しかし、最後に、本番環境をGlassfishとWebSphereに移行したと言わざるを得ません。

私はあなたがすでにこれらのページを通過したことを願っています:

a Java Webアプリケーション)のメモリリーク

Tomcatのメモリリークを修正しますか?

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ほど軽量ではないため、起動に時間がかかりますが、後の作業は少し簡単です。

6
Naveed Kamran

この問題に関する私の経験から、Tomcatが古いクラスローダーを適切にGCするのを妨げていたのは、私が使用していたいくつかのフレームワークが作成していた(そして適切に処理しなかった)いくつかのThreadLocalsでした。

ここで説明されているものに似たもの: ThreadLocal&Memory Leak

私はこのThreadLocalsを適切にファイナライズしようとしましたが、リークによってALOTが減少しました。それでもリークはありましたが、以前の10倍の再デプロイを処理できました。

どういうわけかThreadLocalsに接続できるオブジェクトへのメモリダンプを確実にチェックします(特に、トランザクションを制御するために何かを使用する場合、またはスレッドで分離されているものを使用する場合は、非常に一般的です)。

お役に立てば幸いです。

1
BrunoJCM

Tomcatは通常、実稼働環境では適切なオプションではありません。いくつかの本番アプリケーションでTomcatを使用していましたが、ヒープサイズやその他の構成が適切に設定されていても、アプリケーションをリロードするたびに、メモリ消費量が増加することがわかりました。 Tomcatサービスを再起動しない限り、メモリは完全に再利用されません。ログのクリア、すべてのアプリの再デプロイ、月に1回または週に1回、最も忙しくない時間帯に定期的にTomcatを再起動するなど、このようなすべての実験をテストしました。しかし、最後に、本番環境をGlassfishとWebSphereに移行したと言わざるを得ません。

0
Wael Mofreh

Tomcatの再展開でのメモリリークは非常に古い問題です。これを解決する唯一の実際の方法は、アプリケーションを再デプロイする代わりにTomcatを再起動することです。複数のアプリがある場合は、異なるポートで複数のTomcatのサービスを実行し、nginxに参加させる必要があります。

0
A.Alexander

ClassLoaderがガベージコレクションされるのを防ぐThreadLocalの使用を確認してください。 ThreadLocal値でクラスへの参照を削除するか、ThreadLocalの代わりに https://github.com/codesinthedark/ImprovedThreadLocal を使用してください

0
CodesInTheDark

数百のTomcatインスタンスがいくつかの環境(本番環境でも)で実行されており、この問題に対して私たちが見つけた唯一の合理的な解決策は、設定された時間にすべてのTomcatを停止して再起動することです毎日(夜間) 。

私たちは多くのトリックを試しましたが、これは稼働時間の要件に対する永続的なソリューションです。

0
Marc.S