JDBC内存泄漏最常见原因是ResultSet和Statement未关闭,导致堆内ResultSetImpl堆积及堆外RSS上涨;应通过MAT分析堆转储、代码检查try-with-resources使用、运行态监控RSS与socket数、禁用DriverManager直连并启用连接池泄漏检测来排查和修复。

直接查 ResultSet 和 Statement 的关闭缺失,是 JDBC 内存泄漏最常见、最快暴露的入口。Statement 未关闭本身不直接占堆内存,但它会阻止底层 ResultSet、Socket 缓冲区、驱动元数据缓存等资源释放,最终引发堆内(ResultSetImpl 大量堆积)和堆外(RSS 持续上涨)双泄漏。
看堆转储:找 ResultSetImpl 和 PreparedStatement 的异常堆积
用 jmap -dump:format=b,file=heap.hprof <pid> 抓取堆快照,用 MAT 打开后重点看:
- “Leak Suspects” 报告里是否出现大量 com.mysql.cj.jdbc.result.ResultSetImpl 或 com.mysql.cj.jdbc.ClientPreparedStatement
- 按 class 名排序,查看 ResultSetImpl 实例数是否随轮询次数线性增长
- 检查这些 ResultSetImpl 的引用链——是否都指向某个长期存活的 Statement 或 Connection,而该 Statement 并未被 close
查代码:确认所有 ResultSet 都在 try-with-resources 中声明
Statement 未关闭常源于“只关 Connection,漏掉 ResultSet 和 Statement”。高频轮询场景下尤其危险。正确写法必须把三者都纳入资源管理:
Java开发手册规约集合,基于阿里巴巴Java开发手册(嵩山版)。 涵盖7大维度:编程规约、异常日志、单元测试、安全规约、MySQL数据库、工程结构、设计规约。 当用户需要:(1) 编写或审查Java代码 (2) 检查命名/代码规范 (3) 处理异常和日志 (4) 编写单元测试 (5) 安全编码 (6) 数据库设...
- ❌ 错误写法:
Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(...);—— rs 和 stmt 都没 close - ✅ 正确写法:
try (Connection conn = ds.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql); ResultSet rs = pstmt.executeQuery()) { ... } - 特别注意:不要只写
try (Connection c) {...}就认为安全;只要 ResultSet 或 PreparedStatement 是在 try 块外声明或返回,就大概率泄漏
验运行态:观察 RSS 与堆内存增长是否分离
如果堆内存(jstat -gc)稳定,但进程 RSS(ps aux --sort=-rss)持续缓慢上升,说明问题不在堆内对象,而在堆外资源绑定:
立即学习“Java免费学习笔记(深入)”;
- 这往往意味着 Statement/ResultSet 虽已“逻辑关闭”,但其背后的 Socket 缓冲区、MySQL 驱动本地句柄仍未释放
- 此时要配合 lsof -p <pid> | grep IPv4 | wc -l 查 socket 数是否同步增长
- 再查 MySQL 端:SHOW PROCESSLIST 中是否存在大量 State = Reading from net 且 Time > 60 的连接——它们正是未读完结果集或未关闭 Statement 的物理证据
堵源头:禁用直连,强制走连接池并启用泄漏检测
绕过连接池的 DriverManager.getConnection() 调用是 Statement 泄漏高发区,因为完全脱离自动回收机制:
- 在代码中全局搜索 DriverManager.getConnection,全部替换为 DataSource 获取
- HikariCP 配置开启泄漏检测:spring.datasource.hikari.leak-detection-threshold=30000(30 秒)
- 一旦触发告警,日志会打印完整调用栈,精准定位到哪一行 new Statement / executeQuery 后没 close

















