先看句柄数是否持续上涨,再结合类型和归属定位泄漏源头;用lsof定期采样画曲线判断真泄漏,对比FDSize与实际数排除上限误判,统计句柄类型锁定异常源,关联线程栈追踪代码层,修复后需压测验证并加阈值监控。

直接看句柄数是否持续上涨,再结合类型和归属定位泄漏源头。不能只盯着“Too many open files”报错,得把增长趋势、句柄类型、对应资源三者串起来看。
确认句柄是否真在泄露
先别急着查代码,先验证是不是真泄漏:
- 用 lsof -p PID | wc -l 定期采样(比如每分钟一次),画出随时间变化的曲线;稳定增长就是典型泄漏,锯齿波动或平台期则大概率正常
- 对比 cat /proc/PID/status | grep 'FDSize\|Threads' 中的 FDSize(内核分配的句柄槽位)和实际打开数,若后者远小于前者,说明不是上限问题,而是资源没释放
- 检查系统级限制:ulimit -n 看当前进程软限制,cat /proc/PID/limits | grep files 看硬限制,排除单纯配得太低的误判
锁定异常句柄类型和归属
光知道数量涨没用,得知道“谁在涨”:
Linux系统管理专家,覆盖12大模块:用户权限、SSH、存储、网络、systemd、防火墙、日志监控、备份恢复、TLS证书、Ansible、容器、IaC。提供配置、验证、加固、监控、备份、自动化、故障排查、回滚闭环。关键词:useradd、sudo、sshd_config、chmod、SEL...
- 执行 lsof -p PID | awk '{print $5}' | sort | uniq -c | sort -nr,快速统计各类句柄(如 IPv4、REG、PIPE、anon_inode)占比,大量重复的 IPv4 或 socket 类型往往指向连接未关闭
- 对高频类型进一步过滤:lsof -p PID -a -iTCP -sTCP:ESTABLISHED 查未关闭的 TCP 连接,lsof -p PID | grep REG | head -20 看是否堆积临时文件或日志句柄
- 注意“deleted”标记:lsof -p PID | grep deleted,这类已删但未 close 的文件会持续占句柄,常见于滚动日志没正确 flush+close
关联到具体线程和调用路径
找到可疑句柄后,要追到代码层:
- 用 ls -l /proc/PID/fd/ 列出所有 fd 符号链接,结合 inode 号反查文件或 socket 绑定信息
- 若服务支持 jstack(Java)或 pstack(C/C++),在句柄数高位时抓线程栈:pstack PID > stack.log,重点找阻塞在 accept、read、write 或 close 调用附近的线程
- 对 Netty、Tomcat 等框架,检查是否有未设置超时的 ChannelFuture、未调用 release() 的 ByteBuf、或漏掉 channel.close() 的异常分支
验证修复与长期监控
改完代码不能只测一次,得闭环验证:
- 上线后加轻量级监控:用脚本定时跑 lsof -p PID | wc -l,超过阈值(比如 3000)就告警,比等崩溃更早干预
- 在测试环境模拟长稳态压测,持续运行 24 小时以上,观察句柄曲线是否收敛而非爬升
- 对关键服务,在启动脚本里加 echo "Max open files: $(cat /proc/self/limits | grep 'Max open files' | awk '{print $4}')" >> /var/log/app/startup.log,留档基线

















