29

Eclipse Juno で Java EE Web アプリケーションを開発しています。PostgreSQL データベースとともに JDBC 接続プール (org.apache.tomcat.jdbc.pool) を使用するように Tomcat を構成しました。私のプロジェクトの 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 個の接続のみが予約されます。

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

助けていただければ幸いです。

PS Apache Tomcat バージョン 7.0.32

4

1 に答える 1

36

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

この問題を解決するには、context.xml ファイルのResource要素に値「close」を持つ属性closeMethod(ここに記載) を追加します。

/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 に関連して) 対処する必要がある瞬間が 1 つあります。完全な説明については、UPDATE 3 を参照してください。


長い答え

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

=== UPDATE 1 (2012-12-03) 別名「新たな希望」 ===

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

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

したがって、古い Tomcat バージョン (7.0.34 未満) を使用している場合は、上記の解決策を使用してください。それ以外の場合、Apache Tomcat バージョン 7.0.34 以降では、私が説明したような問題は発生しないはずです。(更新 2 を参照)

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

私のバグ レポートで最初に説明した問題は、現在最新の Apache Tomcat バージョン 7.0.50 でもまだ存在しているようで、Tomcat 7.0.47 でも再現しました (指摘してくれたMiklos Krivanに感謝します)。現在、Tomcat はリロード後に追加の接続を閉じることができたり、1 回のリロード後に接続数が増加して安定した状態を維持したりすることがありますが、最終的にはこの動作は依然として信頼できません。

最初に説明した問題を再現できました (ただし、これも簡単ではありません。連続するリロードの頻度に関連している可能性があります)。これは時間の問題のようです。つまり、Tomcat がリロード後に十分な時間があれば、多かれ少なかれ必要に応じて接続プールを管理します。Mark Thomas が彼のコメント(引用) で述べたように: (引用の終わり)、速度が決定的な要因のようです。

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

=== UPDATE 3 (2014-01-13) 別名「Tomcat Release Manager の復帰」 ===

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

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

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

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

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

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

結論

この投稿の最初に示した解決策を使用してください (ただし、念のために、それを使用すると理論的に発生する可能性のある JNDI 関連の問題に注意してください)。


代替ソリューション

Michael Osipov は、彼のCloseableResourceListenerを使用することを提案しました。これは、Web アプリケーションのアンデプロイ中に開いたままのリソースによって引き起こされるメモリ リークを防ぎます。だからあなたも試してみてください。


免責事項UPDATES の別名は、スターウォーズの映画シリーズ
に触発されたものです。すべての権利は、それぞれの所有者に帰属します。

于 2012-11-29T17:21:29.157 に答える