ss -m 显示 skmem 陡增但 ps/top RSS 不高,说明内核 socket 缓冲区(sk_rmem_alloc/sk_wmem_alloc)占用内存,不计入进程 RSS;需用 ss -tni、netstat -s、ss -tunmi 结合 inode 和命名空间定位泄漏点。

ss -m 显示 skmem 陡增但 ps/top RSS 不高,说明内核 socket 缓冲区在吃内存
这种情况不是用户态进程占了大量物理内存,而是内核为 TCP socket 分配的接收/发送缓冲区(sk_rmem_alloc、sk_wmem_alloc)持续增长且未释放。典型表现为:ss -m 输出中 rmem/wmem 列动辄几百 MB 甚至 GB,而 ps aux --sort=-rss 或 top 看不到对应进程 RSS 异常——因为这部分内存记在内核 sk_buff 和页缓存上,不计入进程 RSS。
用 ss -i + netstat -s 快速筛出异常 socket 状态和重传行为
先确认是不是大量连接卡在非活跃状态又没及时关闭:
-
ss -tni state established | head -20查看 ESTAB 连接的retrans、rto、qloss字段:若retrans持续增长或qloss> 0,说明对端丢包或 ACK 延迟,导致内核不断重传并堆积未确认数据(wmem涨) -
netstat -s | grep -A 5 "Tcp:"关注TcpRetransSegs、TcpOutSegs比值:若重传率 > 5%,大概率是网络路径问题或对端处理慢,触发内核缓冲区自动扩容 - 特别注意
ss -tni state fin-wait-1或time-wait连接数是否异常多:FIN-WAIT-1 卡住意味着本端发了 FIN 但没收到 ACK,socket 仍持有发送缓冲区;TIME-WAIT 虽不占wmem,但若数量极大(>65K),可能掩盖真正泄漏点
定位具体 socket 所属进程:别只信 pid,要抓 inode 和 sock_diag
ss -tunp 显示的 PID 可能不准(比如容器里 PID namespace 隔离、或进程已退出但 socket 还在)。更可靠的方式是:
- 用
ss -tunmi输出中的ino字段(即 socket inode 号),再查lsof -nPi | grep或inofind /proc/*/fd -lname "socket:[ino]" 2>/dev/null - 若系统支持,用
ss -tunmi -o加上定时戳,观察同一ino的rmem是否随时间线性上涨——这是泄漏铁证 - 对容器环境,必须进对应 PID namespace 查:比如
nsenter -t,否则看到的是宿主机视角的映射,PID 对不上pid-n ss -tunmi
常见漏点:应用层 close() 后未等 FIN ACK 就 exit,或设置了 SO_LINGER=0
很多泄漏不是代码没调 close(),而是调了但没处理好 TCP 四次挥手的时序:
- 进程 exit 时,内核会强制清理 socket,但如果此时
sk_wmem_alloc > 0(还有未确认数据),socket 会进入FIN-WAIT-1并继续持有发送队列,直到超时(默认 60s × 3)才真正释放——这期间wmem一直不归零 -
setsockopt(fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger))中若linger.l_onoff=1 && linger.l_linger=0,会触发 RST 中断连接,但发送队列里的数据直接丢弃,内核仍需回收 sk_buff 内存,延迟释放明显 - Go 的
net.Conn.Close()默认不等 FIN ACK;Python 的socket.close()同理。真正安全的做法是:先shutdown(SHUT_WR),再循环recv()直到返回 0,最后close()
真正难排查的是那些“半关闭后长期空闲”的连接:它们既不发数据也不收数据,ss -m 却显示 rmem 几十 MB。这时候得怀疑应用是否禁用了 TCP_QUICKACK 或调大了 tcp_rmem,让内核预分配了缓冲区却从未填满——这种内存虽不泄漏,但会虚高显示,需要结合 /proc/net/sockstat 的 used 和 allocated 对比判断。










