运行1个月后Spring Boot应用出现Too many open files异常求助
排查思路
1. 优先排查Files.list的资源泄漏问题
Files.list(Path)返回的Stream<Path>关联了底层的目录文件描述符,必须显式关闭。你的代码中直接调用collect(Collectors.toList())后,Stream未被关闭,即使遍历完成,部分JDK实现仍可能保留底层资源不释放。每10秒泄漏1个文件描述符,1个月累计泄漏约43200个,很容易触发系统的文件描述符上限。
2. 排查bar()方法的资源泄漏
即使你认为没有未关闭的文件操作,也可能存在遗漏:
- 检查
bar()中所有文件读写相关代码,是否有未关闭的InputStream/OutputStream、Reader/Writer、FileChannel等资源。 - 确认异常分支下资源是否被正确关闭(比如未用
try-with-resources或finally块)。
3. 用工具定位泄漏点
- 系统层面:Linux下执行
lsof -p <你的应用PID>查看进程打开的所有文件,或ls /proc/<PID>/fd | wc -l统计当前打开的文件描述符数量,观察是否随时间持续增长。 - Java层面:
- 用
jmap -dump:format=b,file=heap.hprof <PID>导出堆快照,用MAT(Memory Analyzer Tool)分析未被回收的流、文件资源对象,定位泄漏源头。 - 用
jstack <PID>查看线程栈,是否有阻塞的文件操作导致资源无法释放。 - 启用Java Mission Control的飞行记录器,追踪文件描述符的分配与释放流程,精准定位泄漏点。
- 用
预防措施
1. 正确关闭Files.list的Stream
使用try-with-resources语法保证Stream自动关闭,即使发生异常:
@Scheduled(fixedRateString = "10000") public void foo() { try (Stream<Path> dirStream = Files.list(Path.of("mydir"))) { List<Path> ps = dirStream.sorted().collect(Collectors.toList()); for (Path p : ps) { bar(p); } } catch (Exception e) { System.out.println(e.getMessage()); } }
2. 规范所有文件资源操作
所有实现AutoCloseable的资源(流、通道等)都必须用try-with-resources包裹,确保自动关闭。比如bar()方法中的文件操作:
private void bar(Path p) throws IOException { try (InputStream in = Files.newInputStream(p)) { // 业务处理逻辑 } }
3. 优化定时任务逻辑
如果目录文件变更不频繁,改用WatchService监听目录变化,替代定时轮询,减少不必要的资源占用。
4. 调整系统文件描述符上限(应急缓解)
Linux下修改/etc/security/limits.conf,提升进程可打开的文件描述符数量:
* soft nofile 65535 * hard nofile 65535
修改后需重启系统或重新登录用户生效,这只是临时缓解,核心解决手段还是修复资源泄漏。
监控方法
1. 系统层面监控
- 编写定时脚本,定期执行
ls /proc/<PID>/fd | wc -l,将结果写入日志或监控系统,当数值接近阈值(比如最大值的80%)时触发报警。 - 用Prometheus + Grafana配合node_exporter,收集进程的文件描述符使用指标,设置告警规则。
2. Java应用层面监控
- 启用Spring Boot Actuator,通过
jvm.file.descriptors指标监控文件描述符的已使用数、最大值等,结合Prometheus采集数据并告警。 - 自定义监控逻辑:通过
ManagementFactory.getOperatingSystemMXBean()获取系统MXBean,实时获取文件描述符使用情况,上报到内部监控平台。 - 定期用Java Mission Control录制飞行记录,分析资源使用趋势,提前发现泄漏迹象。
内容的提问来源于stack exchange,提问作者chris01
相关产品推荐
相关产品推荐

