Linux 服务器 "Too many open files" 错误怎么办?完整排查与解决指南
问题现象
高并发场景下,服务器突然停止响应新请求,系统日志出现以下错误:
java.io.IOException: Too many open files
或
Error: EMFILE, too many open files
查看监控发现:CPU 和内存正常,但服务拒绝连接。
常见原因
| 原因 | 说明 | |------|------| | 文件描述符耗尽 | 进程打开的文件/TCP连接超过系统限制 | | HttpClient 未复用 | 每次请求新建连接,产生大量 TIME_WAIT | | 连接池配置过大 | 数据库连接池超过实际承载能力 | | 资源未释放 | 流资源在异常路径下未 close() | | 临时端口耗尽 | 大量短连接导致端口不够用 |
排查步骤
步骤1:查看当前进程打开的文件数
# 查看进程ID
ps aux | grep java | grep -v grep
# 查看该进程打开的文件描述符数量
ls /proc/<PID>/fd/ | wc -l
# 或直接使用 lsof
lsof -p <PID> | wc -l
步骤2:查看系统限制
# 查看当前用户的文件描述符限制
ulimit -n
# 查看系统级限制
cat /proc/sys/fs/file-max
# 查看当前系统打开的文件总数
cat /proc/sys/fs/file-nr
步骤3:分析连接状态
# 查看各状态连接数量
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
# 常见输出:
# TIME_WAIT 15000
# ESTABLISHED 500
# CLOSE_WAIT 200
步骤4:定位 TIME_WAIT 问题
# 如果 TIME_WAIT 数量巨大,说明短连接过多
netstat -nt | grep TIME_WAIT | wc -l
# 查看具体是哪个服务产生的连接
netstat -ntp | grep TIME_WAIT | head -20
解决方法
方法1:临时增加文件描述符限制
# 当前会话生效
ulimit -n 65535
# 验证
ulimit -n
方法2:永久修改系统限制
# 编辑 limits.conf
vi /etc/security/limits.conf
# 添加以下内容
* soft nofile 65535
* hard nofile 65535
* soft nproc 65535
* hard nproc 65535
# 如果是 systemd 管理的服务,还需修改
vi /etc/systemd/system.conf
vi /etc/systemd/user.conf
# 添加:
DefaultLimitNOFILE=65535
# 重启生效
reboot
方法3:优化内核参数(解决 TIME_WAIT 过多)
# 编辑 sysctl.conf
vi /etc/sysctl.conf
# 添加以下配置
# 开启 TIME_WAIT 重用
net.ipv4.tcp_tw_reuse = 1
# 开启 TIME_WAIT 快速回收(谨慎使用)
net.ipv4.tcp_tw_recycle = 0
# 缩短 TIME_WAIT 等待时间(默认60秒)
net.ipv4.tcp_fin_timeout = 30
# 增加本地端口范围
net.ipv4.ip_local_port_range = 1024 65535
# 应用配置
sysctl -p
方法4:代码层面修复 HttpClient 复用
// 错误做法:每次请求新建客户端
public String callApi(String url) {
HttpClient client = HttpClient.newHttpClient(); // 不要这样写
// ...
}
// 正确做法:复用 HttpClient 实例
public class ApiClient {
private static final HttpClient CLIENT = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.build();
public String callApi(String url) {
// 使用单例 CLIENT
// ...
}
}
方法5:优化数据库连接池配置
# HikariCP 配置示例
spring:
datasource:
hikari:
maximum-pool-size: 20 # 根据数据库承载能力设置,不要盲目调大
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
leak-detection-threshold: 60000 # 连接泄漏检测
方法6:确保资源正确关闭
// 错误做法:可能遗漏关闭
public void readFile(String path) {
try {
InputStream is = new FileInputStream(path);
// 处理...
is.close(); // 异常时不会执行
} catch (Exception e) {
// 处理异常
}
}
// 正确做法:try-with-resources
public void readFile(String path) {
try (InputStream is = new FileInputStream(path)) {
// 处理...
} catch (Exception e) {
// 处理异常
}
}
验证修复
# 1. 确认限制已生效
ulimit -n
# 输出:65535
# 2. 监控文件描述符使用
watch -n 1 'ls /proc/<PID>/fd/ | wc -l'
# 3. 监控连接状态
watch -n 1 'netstat -n | awk "/^tcp/ {++S[\$NF]} END {for(a in S) print a, S[a]}"'
# 4. 压力测试验证
ab -n 10000 -c 100 http://your-api-endpoint
注意事项
- 不要盲目调大连接池:连接数过多会导致线程切换开销增大,反而降低性能
- tcp_tw_recycle 谨慎使用:在 NAT 环境下可能导致连接问题,建议只用 tcp_tw_reuse
- 修改 limits.conf 后必须重启:或重新登录会话才能生效
- systemd 服务需单独配置:limits.conf 对 systemd 管理的服务不生效
总结
| 问题场景 | 解决方式 | |---------|---------| | 文件描述符限制太低 | 修改 /etc/security/limits.conf | | TIME_WAIT 过多 | 启用 tcp_tw_reuse + 优化连接复用 | | 连接池配置不当 | 根据实际承载能力调整 max pool size | | 资源泄漏 | 使用 try-with-resources 确保关闭 |
一句话总结:Too many open files 本质是资源管理问题,需要从系统限制、内核参数、代码规范三个层面综合治理。
问题求助
没能解决你的问题?直接问我
如果你遇到任何技术问题无法解决,可以在这里提交求助。我会尽快查看并回复你。
支持作者
如果这篇文章帮到了你,可以支持我
扫码打赏,支持我持续更新原创排障文章。

