在Tomcat中启用Context reload ="true"时,JDBC连接池将耗尽连接

inf*_*k01 29 java eclipse tomcat connection-pooling jdbc

我正在Eclipse Juno中开发Java 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>
Run Code Online (Sandbox Code Playgroud)

我的应用程序使用Eclipse部署到Tomcat,在Tomcat的context.xml中,属性reloadable设置为"true",以便在检测到更改时自动重新加载Web应用程序:

<Context reloadable="true">

我注意到,每次上面提到的自动重新加载时,都会保留10个与PostgreSQL db的连接(因为在webapp的context.xml中,initialSize ="10").因此,在10次更改后抛出PSQLException:

org.postgresql.util.PSQLException: FATAL: sorry, too many clients already
...
Run Code Online (Sandbox Code Playgroud)

如果我手动重启Tomcat - 一切都很好,只保留10个连接.

有没有人知道解决这个问题的方法,所以有可能将reloadable设置为"true"并且不会在每次重新加载上下文时导致更多连接汇集?

非常感谢任何帮助.

PS Apache Tomcat版本7.0.32

inf*_*k01 36

解决方案(tl; dr)

为了解决这个问题,添加属性closeMethod(记录在这里与值")接近 "的资源在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>
Run Code Online (Sandbox Code Playgroud)

注意属性closeMethod.我测试了它,现在连接数保持严格,如context.xml文件中所定义!

注意
有一个时刻(与JNDI有关)可以照顾.有关完整说明,请参阅更新3.


答案很长

好的,我发现上述解决方案归功于Apache Tomcat委托人Konstantin Kolinko.我在ASF Bugzilla上将此问题报告为Apache Tomcat错误,结果证明这不是错误(请参阅更新1).

=== 更新1(2012-12-03)又名"新希望" ===

好吧,它仍然是一个错误.Apache Tomcat 7发布经理Mark Thomas 证实了(引用):

"这是在JDBC池内存泄露问题.PoolCleaner实例保留防止它被GC'd到连接池的引用.
......
这已被固定在躯干和的7.0.x,将起纳入7.0.34 ".

因此,如果您有较旧的Tomcat版本(小于7.0.34),请使用上述解决方案,否则,从Apache Tomcat版本7.0.34开始,应该没有像我描述的那样的问题.(见更新2)

=== 更新2(2014-01-13)又名"问题反击" ===

看起来我的错误报告中最初描述的问题仍然存在,即使对于当前最新的Apache Tomcat版本7.0.50,我也用Tomcat 7.0.47复制它(感谢Miklos Krivan指出它).虽然现在Tomcat有时会在重新加载后设法关闭其他连接,但有时连接数会在一次重新加载后增加然后保持稳定,但最终这种行为仍然不可靠.

我仍然可以重现最初描述的问题(虽然不是那么容易:它可能与连续重载的频率有关).看起来这只是时间问题,即如果Tomcat在重新加载后有足够的时间,它会或多或少地管理连接池.正如Mark Thomas在他的评论中所提到的那样:"根据closeMethod的文档,该方法仅用于加速资源的释放,否则这些资源将由GC释放." (引用结束),看起来速度是决定性因素.

当使用Konstantin Kolinko提供的解决方案(使用closeMethod ="close")时,一切正常,并且保留的连接数保持严格,如context.xml文件中所定义.所以看来使用closeMethod ="close"是唯一真正的方式(目前),以避免在上下文重新加载后耗尽连接.

=== 更新3(2014-01-13)又名"Tomcat发布经理的回归" ===

解决了UPDATE 2中描述的行为背后的奥秘.在收到Mark Thomas(Tomcat发布经理)的回复后,现在已经清除了更多细节.我希望这是最后一次更新.所以这个错误确实已经在UPDATE 1中提到了.我在这里发布Mark的回复中的重要部分作为引用(强调我的):

根据评论#4到#6,在调查此错误时发现的实际内存泄漏已在7.0.34开始修复.

在重新加载时未关闭连接的问题是JNDI资源的J2EE规范的结果,因此错误报告的这部分是无效的.我正在恢复此错误的状态以修复以反映确实存在的内存泄漏已得到修复.

为了扩展重新加载后无法立即关闭连接的原因,J2EE规范没有为容器提供告知资源不再需要的机制.因此,所有容器都可以做的是对资源的明确引用并等待垃圾收集(这将触发池​​和关联连接的关闭).垃圾收集有时会由JVM确定,因此这就是为什么在上下文重新加载后关闭连接需要不确定的时间,因为垃圾收集可能在一段时间内不会发生.

Tomcat添加了Tomcat特定的JNDI属性closeMethod,该属性可用于在上下文停止时触发JNDI资源的显式关闭.如果等待GC清理资源是不可接受的,那么只需使用此参数即可.Tomcat默认不使用它,因为它可能会对某些JNDI资源产生意外和不需要的副作用.

如果您希望看到一个标准机制来告诉JNDI资源它们不再需要它们,那么您需要游说J2EE专家组.

结论

只需使用本文开头介绍的解决方案(但是,以防万一,请记住理论上可能因使用它而产生的JNDI相关问题).


替代方案

Michael Osipov建议使用他的CloseableResourceListener,它可以防止在取消部署Web应用程序时因左开放资源而导致内存泄漏.所以你也可以尝试一下.


免责声明
UPDATES的别名受到星球大战电影系列的启发.所有权利属于其各自所有者.

  • 简短的回答是"它取决于".closeMethod的默认值为"close".有可能在不再需要资源时调用JndiResource.close()(如果存在这样的方法)会产生预期的效果但是,因为"close()"不是任何标准API的一部分,如果确实存在,我们无法知道行为是什么.因此,调用此方法的结果可能是"意外和不需要的".关于postgres,如果你使用Tomcat的内置连接池,那么"close()"将在连接池上被调用,这几乎可以肯定你想要的. (2认同)