web-dev-qa-db-ja.com

TomcatでContext reload = "true"が有効になっていると、JDBC接続プールで接続が不足する

Java Eclipse JunoのEE Webアプリケーションを開発しています。Tomcatを構成して、JDBC接続プール(org.Apache.Tomcat.jdbc.pool)とPostgreSQLデータベースを使用します。構成は次のとおりです。私のプロジェクトのMETA-INF/context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <!-- Configuration for the Tomcat JDBC Connection Pool -->
    <Resource name="jdbc/someDB"
        type="javax.sql.DataSource"
        auth="Container"
        factory="org.Apache.Tomcat.jdbc.pool.DataSourceFactory"
        driverClassName="org.postgresql.Driver"
        url="jdbc:postgresql://localhost:5432/somedb"
        username="postgres"
        password="12345"
        maxActive="100"
        minIdle="10"
        initialSize="10"
        validationQuery="SELECT 1"
        validationInterval="30000"
        removeAbandoned="true"
        removeAbandonedTimeout="60"
        abandonWhenPercentageFull="50" />
</Context>

私のアプリケーションはEclipseを使用してTomcatにデプロイされ、Tomcatのcontext.xmlで属性reloadableが "true"に設定され、変更が検出された場合にWebアプリケーションが自動的に再ロードされます。

<Context reloadable="true">

上記の自動リロードが発生するたびに、PostgreSQL dbへの接続がさらに10個予約されていることに気付きました(webappのcontext.xml initialSize = "10"にあるため)。したがって、10回の変更の後、PSQLExceptionがスローされます。

org.postgresql.util.PSQLException: FATAL: sorry, too many clients already
...

Tomcatを手動で再起動した場合-すべて正常で、10接続のみが予約されています。

誰かがこの問題の回避方法を知っているので、リロード可能を「true」に設定して開発し、コンテキストがリロードされるたびに接続をプールしないようにすることは可能でしょうか?

どんな助けにも感謝します。

追伸Apache Tomcatバージョン7.0.32

29
informatik01

ソリューション(tl; dr)

この問題を解決するには、属性closeMethod(ドキュメント ここ )を値 "close"でResourceに追加しますcontext.xmlファイルの要素。

/META-INF/context.xmlファイルの正しい内容は次のとおりです。

<Context>
    <!-- Configuration for the Tomcat JDBC Connection Pool -->
    <Resource name="jdbc/someDB"
        type="javax.sql.DataSource"
        auth="Container"
        factory="org.Apache.Tomcat.jdbc.pool.DataSourceFactory"
        driverClassName="org.postgresql.Driver"
        url="jdbc:postgresql://localhost:5432/somedb"
        username="postgres"
        password="12345"
        maxActive="100"
        minIdle="10"
        initialSize="10"
        validationQuery="SELECT 1"
        validationInterval="30000"
        removeAbandoned="true"
        removeAbandonedTimeout="60"
        abandonWhenPercentageFull="50"
        closeMethod="close" />
</Context>

属性closeMethodに注意してください。私はそれをテストしましたが、接続数はcontext.xmlファイルで定義されているとおりに厳密に保持されています!

[〜#〜]ノート[〜#〜]
処理される可能性のある瞬間(JNDIに関連)があります。詳細については、UPDATE 3を参照してください。


長い答え

OK、Apache Tomcatコミッター Konstantin Kolinko のおかげで上記の解決策が見つかりました。私は この問題 をASF BugzillaのApache Tomcatバグとして報告し、 それはバグではないことがわかりました (UPDATE 1を参照)。

===UPDATE 1(2012-12-03)aka "A New Hope"===

まあ、それはまだバグであることが判明しました。 Mark Thomas 、Apache Tomcat 7リリースマネージャー、 confirmed その(引用):

「これはjdbc-poolのメモリリークバグです。PoolCleanerインスタンスは、接続プールへの参照を保持しているため、GCできません。
...
これはトランクおよび7.0.xで修正され、7.0.34以降に含まれる予定です。 "

したがって、Tomcatのバージョンが古い場合(7.0.34未満)、上記のソリューションを使用してください。それ以外の場合は、 apache Tomcatバージョン7.0.34以降、私が説明したような問題はないはずです。 (UPDATE 2を参照)

===UPDATE 2(2014-01-13)別名「問題の反撃」===

my bug report で最初に説明された問題は、現在の最新のApache Tomcatバージョン7.0.50でもまだ存在しているようで、Tomcat 7.0.47でも再現しました(おかげで Miklos Krivan それを指摘するための)。現在、Tomcatはリロード後に追加の接続を閉じることを管理する場合がありますが、接続の数は1回のリロード後に増加し、その後安定したままになることがありますが、最終的にはこの動作はまだ信頼できません。

まだ最初に説明した問題を再現できます(ただし、それほど簡単ではありません:連続したリロードの頻度に関連している可能性があります)。それは時間の問題だと思われます。つまり、リロード後にTomcatに十分な時間があれば、多かれ少なかれ接続プールを管理します。 Mark Thomasが comment (quote)で述べたように、「closeMethodのドキュメントによると、このメソッドは、GCによって解放されるリソースの解放を高速化するためだけに存在します。」 (引用の終わり)、そしてそれは速度が決定的な要因であるように見えます。

Konstantin Kolinko(closeMethod = "close"を使用)によって提示されたソリューションを使用すると、すべてが正常に機能し、予約された接続の数がcontext.xmlファイルで定義されているとおりに厳密に保持されます。したがって、closeMethod = "close"を使用することが、コンテキストリロード後に接続が不足するのを回避する(現時点では)唯一の真の方法であると思われます。

===UPDATE 3(2014-01-13)別名「Tomcatリリースマネージャの返却」===

UPDATE 2で説明されている動作の背後にある謎が解決されます。 Mark Thomas(Tomcatリリースマネージャー)から reply を受け取った後、詳細はクリアされました。これが最後の更新であることを願っています。したがって、バグは実際に修正されました。これは、UPDATE 1で言及されました。ここでは、Markの返信の重要な部分を引用(ここでは強調)として投稿しています。

このバグの調査中に見つかった実際のメモリリークは、コメント#4から#6に従って7.0.34以降で修正されています。

リロード時に接続が閉じられないという問題は、JNDIリソースのJ2EE仕様の結果であり、バグレポートのこの部分は無効です。このバグの状態を復元して、存在していたメモリリークが修正されたことを反映するように修正しました。

リロード後に接続をすぐに閉じるのに失敗する理由が無効である理由をさらに詳しく説明するために、J2EE仕様では、コンテナーがリソースが不要になったことをリソースに通知するメカニズムを提供していません。したがって、コンテナが実行できることは、リソースへの明確な参照とガベージコレクションを待機することだけです(これにより、プールと関連する接続が閉じられます)。ガベージコレクションは、JVMによって決定された時間に発生するのでこれが、ガベージとしてコンテキストのリロード後に接続が閉じられるまでに不確定な時間がかかる理由です。しばらくの間、収集が行われない場合があります。

Tomcatは、コンテキストが停止したときにJNDIリソースの明示的なクローズをトリガーするために使用できるTomcat固有のJNDI属性closeMethodを追加しました。 GCがリソースをクリーンアップするのを待つことが受け入れられない場合は、このパラメータを使用してください一部のJNDIリソースに予期しない望ましくない副作用が生じる可能性があるため、Tomcatはデフォルトではこれを使用しません。

JNDIリソースに不要になったことを伝えるために提供されている標準メカニズムを見たい場合は、J2EEエキスパートグループに働きかける必要があります。

結論

この投稿の冒頭で紹介したソリューションを使用してください(ただし、念のために理論的に使用すると発生する可能性があるJNDI関連の問題に注意してください)。


代替ソリューション

Michael Osipov 彼の CloseableResourceListener を使用することをお勧めします。これにより、Webアプリケーションのアンデプロイ中に開いたままのリソースによって引き起こされるメモリリークが防止されます。だからあなたもそれを試してみるかもしれません。


免責事項
アップデートのエイリアスは スターウォーズ 映画シリーズに触発されました。すべての権利はそれぞれの所有者に帰属します。

36
informatik01