Tomcat 中的 Java Web 应用程序会定期冻结

2022-09-01 17:47:45

运行 Tomcat (7.0.28) 的 Java Web 应用程序会定期变得无响应。我希望有一些关于可能的罪魁祸首(同步?)的建议,以及一些推荐的工具,用于收集有关崩溃期间发生的事情的更多信息。我积累的一些事实:

  • 当 Web 应用冻结时,tomcat 会继续将请求线程馈送到应用中,但应用不会释放它们。线程池填满最大值(当前为 250),然后后续请求立即失败。在正常操作期间,活动线程永远不会超过 2 或 3 个。

  • 出现问题时,不会将任何类型的错误或异常记录到我们的任何tomcat或Web应用程序日志中。

  • 通过tomcat管理Web应用程序在我们的应用程序上执行“停止”,然后执行“启动”可立即解决此问题(直到今天)。

  • 最近,频率是每天两到三次,尽管今天的情况要糟糕得多,可能是20次,有时不会立即恢复生机。

  • 仅在工作时间内出现问题

  • 在我们的暂存系统上未出现问题

  • 出现问题时,服务器上的处理器和内存使用率保持平稳(并且相当低)。Tomcat 报告了充足的可用内存。

  • 当问题发生时,Tomcat 会继续保持响应。管理Web应用程序运行良好,tomcat继续向我们的应用程序发送请求,直到池中的所有线程都填满为止。

  • 当问题发生时,我们的数据库服务器仍然保持响应。我们使用Spring框架进行数据访问和注入。

  • 当使用率较高时,通常会出现问题,但使用量中永远不会出现异常高的峰值。

  • 问题历史:大约一年半前发生了类似的事情。经过许多服务器配置和代码更改,问题直到大约一个月前才消失。在过去的几周内,它发生的频率要高得多,平均每天2或3次,有时连续几次。

  • 我今天确定了一些可能不是线程安全的服务器代码,并为此进行了修复,但问题仍在发生(尽管频率较低)。这是非线程安全代码可能导致的问题吗?

更新:有几篇文章建议数据库连接池耗尽,我朝这个方向进行了一些搜索,发现了另一个Stackoverflow问题,它解释了我遇到的几乎所有问题。

显然,Apache的BasicDataSource实现中maxActive和maxIdle连接的默认值分别为8。此外,maxWait 设置为 -1,因此当池耗尽并且传入新的连接请求时,它将永久等待而不会记录任何类型的异常。我仍然会等待这个问题再次发生,并在JVM上执行jstack转储,以便我可以分析该信息,但看起来这就是问题所在。它唯一没有解释的是为什么该应用程序有时无法从此问题中恢复。我想这些请求有时会堆积起来,一旦它落后,它就永远无法赶上。

更新二:我在崩溃期间运行了一个jstack,发现了以下大约250个(最大线程数):

"http-nio-443-exec-294" daemon prio=10 tid=0x00002aaabd4ed800 nid=0x5a5d in Object.wait() [0x00000000579e2000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        at java.lang.Object.wait(Object.java:485)
        at org.apache.commons.pool.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:1118)
        - locked <0x0000000743116b30> (a org.apache.commons.pool.impl.GenericObjectPool$Latch)
        at org.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:106)
        at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044)
        at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111)
        at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:77)
        at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:573)
        at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:637)
        at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:666)
        at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:674)
        at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:718)

在我未经训练的眼中,这看起来相当有说服力。看起来数据库连接池已达到其上限。我配置了三秒的 maxWait,而不修改 maxActive 和 maxIdle,只是为了确保我们开始看到池填满时记录的异常。然后,我将把这些值设置为适当的值并进行监视。

更新三:配置 maxWait 后,我开始在日志中看到这些内容,如预期的那样:

 org.apache.commons.dbcp.SQLNestedException: Cannot get a connection, pool error Timeout waiting for idle object
        at org.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:114)
        at org.apache.commons.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044)
        at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111)
        at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:77)

我已将maxActive设置为-1(无限),将maxIdle设置为10。我将监视一段时间,但我的猜测是,这是问题的结束。


答案 1

根据经验,您可能希望查看数据库连接池的实现。可能是数据库具有足够的容量,但应用程序中的连接池仅限于少量连接。我不记得细节,但我似乎记得遇到过类似的问题,这是我改用BoneCP的原因之一,我发现在负载测试下它非常快速可靠。

尝试下面建议的调试后,请尝试增加池中可用的连接数,看看这是否有任何影响。

我今天确定了一些可能不是线程安全的服务器代码,并为此进行了修复,但问题仍在发生(尽管频率较低)。这是非线程安全代码可能导致的问题吗?

这取决于你所说的线程安全是什么意思。在我看来,您的应用程序似乎正在导致线程死锁。您可能希望运行生产环境,将 JVM 配置为允许调试器连接,然后使用 JVisualVM、JConsole 或其他分析工具(YourKit 是出色的 IMO)来了解您拥有的线程以及它们正在等待的线程。


答案 2

推荐