29

我正在 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>

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

<Context reloadable="true">

我注意到,每次发生上述自动重新加载时,都会保留 10 个与 PostgreSQL 数据库的连接(因为在 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 相关)需要注意。有关完整说明,请参阅更新 3。


长答案

好的,感谢 Apache Tomcat 提交者Konstantin Kolinko ,我找到了上述解决方案。我将此问题报告为 ASF Bugzilla 上的 Apache Tomcat 错误,结果证明这不是错误(请参阅更新 1)。

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

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

“这是 jdbc-pool 中的内存泄漏错误。PoolCleaner 实例保留对 ConnectionPool 的引用,以防止它被 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 发布经理)的回复后,现在已经清除了更多细节。我希望这是最后一次更新。因此,正如更新 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 的别名受到星球大战电影系列的启发。所有权利均属于其各自所有者。

于 2012-11-29T17:21:29.157 回答