Connection.close()仅标记连接为可复用,实际归还不由应用控制,而取决于连接池状态及Oracle网络层(如TNS半开连接、SQLNET.EXPIRE_TIME不匹配);驱动隐式缓存、配置错位与高负载共同放大泄漏风险。
oracle连接池泄露不是“连接没关”,而是“关了但没真正还回去”或“还回去后被池误判为不可用”,高负载下放大所有隐性缺陷。
调用 connection.close() 时,JDBC 驱动只是把连接标记为“可复用”,实际是否归还、何时归还、能否归还,完全取决于连接池实现和当前连接状态。Oracle 场景下尤其危险:
close() 可能静默阻塞或失败implicitCachingEnabled=true,若上一个业务未调用 preparedStatement.clearCache(),复用该连接时缓存持续膨胀,最终触发 ORA-01000: maximum open cursors exceeded
SQLNET.EXPIRE_TIME=600)不匹配,导致连接在池中“假活”数小时低流量时不易暴露的问题,在并发请求激增时集中爆发:
connection-timeout 设太小(如 1000ms):大量线程卡在“取连接”阶段,监控显示活跃连接数不涨,但应用整体 hang 住;设太大(如 60000ms)则故障感知延迟,DBA 看到数据库会话数飙升却找不到源头testOnBorrow 后未配 connection-init-sql 或 keepaliveTime:连接复用前无轻量校验,首次执行 SQL 时才暴露 TNS 错误,此时已进入业务逻辑,finally 块里的 close() 可能因异常跳过removeAbandonedOnBorrow=true 已废弃,但老配置残留:它会在借连接时强制回收“疑似泄露”的连接,而高负载下判断失准,频繁回收健康连接,引发雪崩式重连和游标泄漏别只盯着应用日志——直接查数据库侧真实连接状态:
SELECT sid, serial#, username, status, sql_id, last_call_et FROM v$session WHERE username IS NOT NULL AND status = 'ACTIVE' AND last_call_et > 300,找出空闲超 5 分钟却仍为 ACTIVE 的会话(典型假活连接)v$open_cursor 中各 session 的打开游标数,若某几个 sid 游标数持续 > 300,基本锁定对应应用线程未清理 PreparedStatement
v$process 的 pga_used_mem 和 v$session 的 program 字段,确认高内存占用进程是否来自 Java 应用(如 jdbc thin client)PreparedStatement 而非复用,是隐式缓存失效或未使用 prepareStatement 缓存的信号这些不是“建议”,是 Oracle 生产环境连接池的底线配置:
立即学习“Java免费学习笔记(深入)”;
connection-timeout=30000(≤ DBA 设置的 SQLNET.EXPIRE_TIME * 1000),validation-timeout=3000,keepaliveTime=300000(5 分钟心跳),禁用 testOnBorrow,启用 connection-init-sql=ALTER SESSION SET CURRENT_SCHEMA=xxx
?implicitCachingEnabled=false&oracle.jdbc.useFetchSizeWithLongColumn=true,避免驱动层缓存干扰连接池管理Connection 获取必须包裹在 try-with-resources 中,禁止在 if/else 分支里写 close();若用 finally,确保 connection != null && !connection.isClosed() 双重检查最易被忽略的是:Oracle 连接池泄露往往不是单点代码 bug,而是连接池参数、驱动行为、数据库网络策略、应用编码习惯四者错位叠加的结果。查问题时别只盯着 Java 堆栈,先看 v$session 和 v$open_cursor——数据库比你的应用更清楚谁拿了连接不还。