commons-dbcp高负载下XATransaction连接泄漏问题咨询
高负载分布式事务下 Commons DBCP 连接泄漏与修改后 StackOverflowError 问题分析
问题背景
我们在高负载分布式事务场景中使用最新版 commons-dbcp + EclipseLink + Narayana 做事务协调,偶尔会出现连接泄漏问题,伴随废弃连接的栈跟踪信息。我们怀疑问题出在 ManagedConnection#updateTransactionStatus 方法上,以下是相关代码和我们的修改:
原代码实现
// ManagedConnection#updateTransactionStatus if (transactionContext != null) { if (transactionContext.isActive()) { if (transactionContext != transactionRegistry.getActiveTransactionContext()) { throw new SQLException("Connection can not be used while enlisted in another transaction"); } return; } // 注释说明:事务本该由 TransactionContextListener 清理,但极少数情况下其他监听器会在我们的监听器之前使用连接 // 这种罕见场景下,现在触发事务完成的调用 transactionComplete(); }
我们的修改
将 transactionComplete(); 移至 else 分支后,连接泄漏问题不再出现:
if (transactionContext != null) { if (transactionContext.isActive()) { if (transactionContext != transactionRegistry.getActiveTransactionContext()) { throw new SQLException("Connection can not be used while enlisted in another transaction"); } return; } } else { transactionComplete(); }
我们面临的两个问题
- 作为非专家,我们担心该修改会破坏原有功能(虽然 DBCP 单元测试全部通过);
- 修改后偶尔会出现如下
StackOverflowError异常:
java.lang.StackOverflowError: null at java.util.concurrent.atomic.AtomicLong.toString(AtomicLong.java:313) at java.lang.String.valueOf(String.java:2994) at java.util.Arrays.toString(Arrays.java:4571) at org.apache.commons.pool2.impl.BaseGenericObjectPool$StatsStore.toString(BaseGenericObjectPool.java:1158) at java.lang.String.valueOf(String.java:2994) at java.lang.StringBuilder.append(StringBuilder.java:131) at org.apache.commons.pool2.impl.BaseGenericObjectPool.toStringAppendFields(BaseGenericObjectPool.java:1328) at org.apache.commons.pool2.impl.GenericKeyedObjectPool.toStringAppendFields(GenericKeyedObjectPool.java:1601) at org.apache.commons.pool2.BaseObject.toString(BaseObject.java:31) at org.apache.commons.dbcp2.PoolingConnection.toString(PoolingConnection.java:536)
恳请熟悉 commons-dbcp 的专家提供技术反馈。
专家技术反馈
1. 该修改是否会破坏原有功能?
首先拆解原代码的设计意图:
- 注释已经明确说明:在极少数高并发场景下,其他事务监听器可能会先于 DBCP 自带的
TransactionContextListener访问连接,导致事务上下文未被及时清理。原代码中的transactionComplete()调用正是为了处理这种边缘情况——当连接仍绑定着一个已 inactive 的事务上下文时,主动触发清理。
你的修改完全改变了这一逻辑:
- 现在仅当没有任何事务上下文(
transactionContext == null)时才调用transactionComplete(),这等于移除了原代码针对那个罕见边缘场景的安全防护。 - 单元测试能通过,是因为它们几乎不会覆盖「其他监听器抢先访问连接」这种高并发边缘场景。在低并发环境下你的修改可能正常工作,但高负载下可能会出现:
- 事务完成后,连接仍处于 enlisted 状态无法复用
- 意外抛出「连接已在另一个事务中」的
SQLException
不过你的修改确实解决了连接泄漏,这说明原代码在你的特定场景下可能存在过度清理的问题——比如 Narayana 事务上下文的生命周期比 DBCP 预期的更长,导致 transactionComplete() 被频繁调用,干扰了连接池的正常运作。
2. 如何解决修改后出现的 StackOverflowError?
从栈跟踪来看,问题出在连接池组件的 toString() 方法循环调用上:
PoolingConnection.toString()会调用连接池的toString(),而连接池的toString()又会尝试序列化统计信息(StatsStore),后者又会引用回连接池,形成无限循环的字符串拼接,最终撑爆栈空间。
为什么修改后会触发这个问题?
transactionComplete()包含大量清理工作:将连接从事务中解除绑定、重置连接状态、归还到连接池。如果现在调用这个方法时,连接或连接池的内部状态还未准备好(比如处于不一致的中间状态),就可能触发原本不会执行的 debug 日志或toString()调用。- 另一种可能是:现在仅在
transactionContext == null时调用transactionComplete(),时机比原代码更晚,可能导致连接归还到池时,池的内部状态正在被修改,引发竞态条件,触发循环的toString()调用。
可尝试的修复方案:
- 调整日志级别:如果你的日志级别是 DEBUG 或 TRACE,commons-dbcp/pool2 会频繁调用连接/池的
toString()方法。尝试将org.apache.commons.dbcp2和org.apache.commons.pool2的日志级别调低到 INFO 或 WARN,看看 StackOverflowError 是否消失。 - 检查 Narayana 与 DBCP 的集成配置:由于使用了 JTA 协调器(Narayana),要确保 Narayana 在 DBCP 处理事务上下文之前,已经正确清理了事务上下文。可能是 Narayana、DBCP、EclipseLink 的集成配置存在问题,导致 inactive 的事务上下文残留。
- 替代修改方案(保留原逻辑的同时修复泄漏):不要将
transactionComplete()移到 else 分支,而是给原调用添加一个防护条件,避免不必要的清理:
这种方式既保留了原代码对边缘场景的处理,又避免了可能导致泄漏的过度清理操作。if (transactionContext != null) { if (transactionContext.isActive()) { if (transactionContext != transactionRegistry.getActiveTransactionContext()) { throw new SQLException("Connection can not be used while enlisted in another transaction"); } return; } // 添加防护:仅当事务上下文未被 enlisted 时才执行清理 if (!transactionRegistry.isEnlisted(transactionContext)) { transactionComplete(); } }
最终建议
你当前的修复解决了泄漏问题,但牺牲了边缘场景的防护,还引入了新的 StackOverflowError。更稳妥的方式是深入排查原泄漏的根因——大概率是 Narayana 的事务生命周期与 DBCP 的清理逻辑不匹配。可以检查以下几点:
- Narayana 的事务超时设置是否合理
- EclipseLink 是否在事务完成后正确释放连接
- Narayana 的监听器执行顺序是否优先于 DBCP 的监听器
尽量避免修改 DBCP 核心代码,通过调整集成配置来解决问题会更可靠。
内容的提问来源于stack exchange,提问作者rgoncalves
相关产品推荐
相关产品推荐

