java数据库连接未关闭会导致堆外内存持续泄漏,gc无法回收,引发jvm rss内存暴涨、系统oom或连接雪崩;根本原因是未关闭的tcp socket长期占用内核缓冲区,且jvm失去对底层网络资源的控制权。

Java 中数据库连接未关闭,表面看只是“连不上数据库”或报 Too many connections,实际更危险的是它会引发**堆外内存(Off-heap)持续泄漏**,且 GC 完全无法回收——这种泄漏不体现在堆内存监控里,容易被忽略,最终导致 JVM 进程 RSS 内存暴涨、系统级 OOM 或连接雪崩。
底层网络缓冲区被长期占用
每个未关闭的 Connection 对应一个 TCP socket,操作系统为其分配接收缓冲区(sk_rmem_alloc)和发送缓冲区。只要连接没断开,内核就一直保留这些缓冲区内存。Java 层面的 Connection 对象即使被 GC 回收,只要底层 socket 还开着,这部分堆外内存就一直挂着。
- MySQL 中
SHOW PROCESSLIST显示大量 Sleep 或 Reading from net 状态,且Time值持续增长 → 说明连接空闲但未释放,缓冲区正在积压数据 - Linux 下执行
cat /proc/<pid>/maps | grep rw | awk '{sum += $3}'</pid>,若可写内存映射区随运行时间线性上涨 → 堆外资源堆积确凿证据 -
pstack <pid></pid>发现大量线程卡在SocketInputStream.socketRead0或EPollArrayWrapper.epollWait→ 连接挂起,等待网络数据,缓冲区持续驻留
连接池机制被绕过或失效
手动调用 DriverManager.getConnection() 直连数据库,完全绕过连接池管理,这类连接生命周期由 TCP 栈控制,JVM 无感知;而使用连接池时,若未在 finally 块中归还,或 try-with-resources 作用域写错,连接就“借走不还”,池中活跃数不断攀升。
- HikariCP 的
leak-detection-threshold设为 30000(30 秒),超时未归还会打印完整调用栈,精准定位到哪一行getConnection()没还 -
ss -tulpn | grep :3306查出 ESTABLISHED 连接数远超maxPoolSize,再对比lsof -p <pid> | grep IPv4 | wc -l</pid>,若两者同步增长 → 泄漏已发生 - 未启用
log-connection-statement,就看不到每次获取/归还日志,问题难以复现和追踪
关联资源未释放形成连锁泄漏
一个 Connection 往往关联 Statement 和 ResultSet。如果只关了 Connection,但 ResultSet 里还有未读完的数据包,底层 socket 缓冲区仍可能被占用;反过来,如果 ResultSet 没关,其持有的 Connection 引用也可能延迟释放。
- 推荐统一用
try-with-resources,按声明顺序逆序自动关闭:Connection → Statement → ResultSet,无需手写finally - 避免在循环中反复
conn.createStatement()却不 close,尤其在异常分支里遗漏关闭逻辑 - ORM 框架(如 MyBatis)也需注意:显式开启的
SqlSession必须手动close(),否则底层 Connection 不会归还
本质不是 Java 对象没被回收,而是 JVM 失去了对底层网络资源的控制权。一旦 socket 打开却不关,内存就从操作系统层面被锁死,GC 再强也无能为力。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











