数据库连接未及时归还会导致连接池耗尽,引发请求阻塞、超时、500错误及数据库“Too many connections”异常,同时造成堆外内存与文件句柄泄漏,需通过try-with-resources+leakDetectionThreshold+JMX监控协同防治。

数据库连接未及时归还连接池,不是“多占点内存”那么简单——它会快速引发级联故障,轻则请求超时、响应变慢,重则整个服务不可用。
连接池迅速耗尽,新请求全部阻塞
连接池有固定上限(比如 HikariCP 默认 maxPoolSize=10)。一旦某段代码漏关连接,该连接就一直被占用,无法回到空闲队列。持续积累后,活跃连接数涨到上限,后续所有 getConnection() 调用都会:
- 在获取连接时阻塞,直到超时(默认30秒)
- 抛出 SQLException:“Connection is not available, request timed out after Xms”
- Web 请求直接返回 500 或超时,用户看到“系统繁忙”
数据库服务器压力陡增,拖垮底层
每个未归还的连接,都在数据库端维持一个真实会话(session)。这些“僵尸连接”会:
- 持续占用数据库内存和线程资源
- 可能持有未提交事务,导致锁表或长事务阻塞其他操作
- 触发数据库连接数限制(如 MySQL 的 max_connections),拒绝新接入
- 日志中频繁出现 “Too many connections” 错误
内存与句柄泄漏,进程逐渐僵死
Java 层面,Connection 对象本身虽小,但背后封装了 Socket、ByteBuffer、驱动内部状态等资源。长期不 close 会导致:
立即学习“Java免费学习笔记(深入)”;
- 堆外内存(DirectBuffer)无法释放,触发 Full GC 频繁甚至 OOM
- 操作系统文件句柄(file descriptor)被持续占用,达到 ulimit 限制后,连日志写入、新线程创建都会失败
- 连接池内部维护的连接元数据(如创建时间、持有栈信息)持续累积,加剧堆内存压力
隐蔽难查,问题延迟爆发
这类泄漏往往不会立刻显现。可能上线初期一切正常,运行几小时或几天后才突然恶化。原因包括:
- 泄漏只发生在特定分支(如异常路径、定时任务、异步回调中)
- 连接池启用了 leakDetectionThreshold(如 30 秒),但日志仅告警,未引起重视
- 监控缺失:没看 activeConnections、idleConnections、threadsAwaitingConnection 等关键指标
修复核心:自动释放 + 主动监控
靠人工检查 close() 几乎必然遗漏。真正有效的做法是两层防御:
- 编码层:统一使用 try-with-resources,确保 Connection、Statement、ResultSet 按逆序自动关闭
- 配置层:开启连接泄漏检测(leakDetectionThreshold=30000)、设置合理 idleTimeout(如 10 分钟)、启用 JMX 监控连接池实时状态
- 运维层:在 Grafana 中配置告警——当 activeConnections / maxPoolSize > 0.9 并持续 2 分钟,立即通知


















