当在 Tomcat 中启用上下文 reload=“true” 时,JDBC 连接池的连接用完解决方案长答案结论替代解决方案

2022-09-01 13:13:37

我正在Eclipse Juno中开发一个Java EE Web应用程序。我已经将Tomcat配置为使用JDBC连接池(org.apache.tomcat.jdbc.pool)以及PostgreSQL数据库。以下是我项目的 META-INF/上下文中的配置.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 的上下文中.xml可重装属性设置为“true”,以便在检测到更改时自动重新装入 Web 应用程序:

<Context reloadable="true">

我注意到,每次发生上述自动重新加载时,都会保留10个与PostgreSQL db的连接(因为在webapp的上下文中.xml initialSize=“10”)。因此,在进行 10 次更改后,将抛出一个 PSQLException:

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

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

有没有人知道解决这个问题的方法,所以有可能将可重新加载设置为“true”进行开发,并且每次重新加载上下文时都不会导致池化更多连接?

将不胜感激任何帮助。

附言 Apache Tomcat 版本 7.0.32


答案 1

解决方案

为了解决此问题,请将值“close”的属性(记录在此处)添加到上下文中的 Resource 元素.xml文件中。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。我测试了它,现在连接数严格按照上下文中定义.xml文件!

注意
有一个时刻(与 JNDI 相关)可以处理。有关完整说明,请参阅 UPDATE 3。


长答案

好的,我找到了上述解决方案,这要归功于Apache Tomcat提交者Konstantin Kolinko。我在ASF Bugzilla上将此问题报告为Apache Tomcat错误,事实证明这不是一个错误(请参阅更新1)。

=== 更新1 (2012-12-03) 又名 “新的希望” ===

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

“这是jdbc-pool中的内存泄漏错误。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”)时,一切都很好,并且保留的连接数严格按照上下文中的定义进行保留.xml文件。因此,使用 closeMethod=“close” 似乎是(目前)避免在上下文重新加载后连接耗尽的唯一正确方法。

=== 更新3(2014-01-13)又名“Tomcat发布管理器的回归” ===

UPDATE 2 中描述的行为背后的谜团已解开。在我收到Mark Thomas(Tomcat发布经理)的回复后,更多细节现在已经清除。我希望这是最后一次更新。因此,正如UPDATE 1中提到的那样,该错误确实已得到修复。我在这里发布Mark回复中的重要部分作为引用(强调我的):

调查此错误时发现的实际内存泄漏已在7.0.34及更高版本中根据注释#4至#6进行了修复。

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

为了进一步说明为什么在重新加载后无法立即关闭连接是无效的,J2EE 规范没有为容器提供任何机制来告诉不再需要它的资源。因此,容器所能做的就是清除对资源的引用并等待垃圾回收(这将触发池和关联连接的关闭)。垃圾回收有时会由 JVM 确定,因此这就是为什么在上下文重新加载后关闭连接需要不确定的时间,因为垃圾回收可能在一段时间内不会发生。

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

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

结论

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


替代解决方案

Michael Osipov建议使用他的CloseableResourceListener,它可以防止在取消部署Web应用程序期间由剩余的开放资源引起的内存泄漏。所以你也可以试一试。


免责声明
更新的别名灵感来自星球大战电影系列。所有权利均属于其各自所有者。


答案 2

推荐