Java应用耗尽Oracle连接数的根本原因是连接泄漏,即Connection被借出后未归还;close()仅通知连接池释放,实际是否回收取决于池状态和网络状况,需依赖leak-detection-threshold定位泄漏点并确保ResultSet/Statement/Connection全量关闭。
Java 应用耗尽 Oracle 连接数,**根本不是数据库“连不上”,而是连接被借走后没还回来**。只要看到 activeConnections 持续上涨、QPS 稳定但连接数不降,基本就是泄漏。调用 connection.close() 只是向连接池发个“我用完了”的通知,实际是否回收、何时回收,取决于池当前状态和网络状况。如果连接已半开(比如 TNS 超时、防火墙静默断连),close() 可能静默失败或阻塞,连接就卡在“活跃但不可用”状态。
close() 不抛异常、不返回成功/失败,不能靠它日志判断是否真归还leak-detection-threshold 是唯一能抓到“借了不还”的机制——必须配,且设为 60000(60 秒)较稳妥很多人只关 Connection,漏掉 ResultSet 和 Statement。Oracle 驱动中,这两个对象持有数据库游标(cursor),不显式关闭会直接触发 ORA-01000: maximum open cursors exceeded,比连接池耗尽更早暴露问题。
ResultSet rs = stmt.executeQuery(); 后没调 rs.close() 或 stmt.close()
try-with-resources:try (Connection c = ds.getConnection(); Statement s = c.createStatement(); ResultSet rs = s.executeQuery()) { ... }
PreparedStatement,注意 Oracle 驱动 12.2.0.1+ 默认开启 implicitCachingEnabled=true,缓存绑定在 Connection 上;上一个使用者没调 clearCache(),复用时可能膨胀游标泄漏高发场景永远在 catch 或 finally 缺失的路径里。比如递归调用、SQL 字段名大小写不匹配导致 SQLException 抛出、事务未传播导致 Spring 无法自动 close —— 这些都会让 close() 根本没执行。
if(found) { loginUsersIntoStore(user); } 形成无限递归,每次递归都 new Connection 却不关SELECT id,代码却用 rs.getInt("ID"))在严格模式下直接抛异常,跳过后续 close@Transactional 方法被同类内非 public 方法调用,事务不生效,连接不归还DBA 执行 select count(*) from v$session 看到连接数高,容易误判为“数据库满了”。但真实泄漏点几乎都在应用侧:连接池里的连接明明标记为 idle,却因网络层断连而无法重用,池不断新建连接填坑,最终打满 v$process。
v$session 显示的是数据库视角的会话,v$process 才是进程数上限(对应 processes 参数)activeConnections、idleConnections)是否持续偏离配置值leak-detection-threshold 后,首次触发会在日志末尾打出完整堆栈,定位到具体哪行代码借了不还——这是唯一可靠线索