Socket缓冲区未关闭引发的典型堆外内存泄漏,表现为RSS持续上涨、GC无感、最终系统OOM;需通过RSS增长、pstack线程栈、/proc/pid/maps可写区、HikariCP泄漏检测、ss/lsof与MySQL PROCESSLIST多维度交叉定位。

Socket 缓存区未及时关闭引发的内存泄漏,属于典型的堆外(Off-heap)泄漏——它不占 JVM 堆内存,GC 完全无感,但 RSS 内存会持续上涨,最终触发系统级 OOM 或连接雪崩。排查关键不是找“谁没调 close()”,而是追踪“哪个物理连接一直挂着、缓冲区还在收数据”。
看内存特征:先确认是不是堆外泄漏
如果监控显示:
- jstat -gc
中老年代、元空间增长平缓,但 ps aux --sort=-rss | head -n 10 显示进程 RSS 持续上升 - /proc/
/maps 中可写映射区(grep rw | awk '{sum += $3}')随运行时间线性增长 - pstack
看到大量线程卡在 SocketInputStream.socketRead0 或 EPollArrayWrapper.epollWait
基本可锁定为 Socket 缓冲区堆积,不是堆内对象泄漏。
查连接状态:从 OS 和 DB 双向印证
执行以下命令交叉比对:
立即学习“Java免费学习笔记(深入)”;
- ss -tulpn | grep :3306:看 ESTABLISHED 连接数是否远超 HikariCP 的 maxPoolSize
-
lsof -p
| grep IPv4 | wc -l :Java 进程打开的 socket 数是否同步增长 - 进 MySQL 执行 SHOW PROCESSLIST:重点关注 State 为 Sleep 或 Reading from net、Time > 60s 的连接——这些连接对应的内核接收缓冲区(sk_rmem_alloc)很可能已积压大量未读数据
定位泄漏源头:靠连接池主动兜底
HikariCP 的 leak-detection 不是摆设,要真开、真用:
- 设 spring.datasource.hikari.leak-detection-threshold=30000(单位毫秒,建议为业务最大预期持有时间的 1.5 倍)
- 日志出现 "Connection leak detection triggered" 时,会打印完整调用栈,精准到哪一行 getConnection() 没归还
- 配合 spring.datasource.hikari.log-connection-statement=true,记录每次获取/归还动作,便于回溯上下文
验证释放逻辑:别只关 Connection
很多泄漏卡在“关了 Connection,却漏了 ResultSet”:
- MySQL 8.0+ 驱动默认缓存元数据和部分行数据,ResultSetImpl 不 close,整块结果集就钉在堆里
- 检查所有 try-with-resources 是否漏写了 rs:必须是 try (Connection c = ...; PreparedStatement ps = ...; ResultSet rs = ...) {...}
- 禁止手动 new PreparedStatement 或 DriverManager.getConnection()——绕过连接池的直连,其 Socket 生命周期完全脱离 JVM 管控,GC 无法感知


















