"Files.walk()返回的Stream必须关闭"是JDK文档里明确写出的警告,但这篇不满足于知道这条规则,而是真实用lsof量化出——不关闭到底会泄漏多少、泄漏的量和调用次数之间是什么样的真实精确关系。
上一篇:文本块真实验证
先确认Files.walk()本身的遍历功能是正确的——真实创建一批测试文件,验证遍历计数。
FilesWalkResourceLeakDemo.java Stream<Path> walkStream = Files.walk(root); long count1 = walkStream.count(); // 没有调用close() try (Stream<Path> walkStream2 = Files.walk(root)) { count2 = walkStream2.count(); }
第一次遍历(未关闭Stream)真实统计到: 21 个路径 第二次遍历(try-with-resources正确关闭)真实统计到: 21 个路径
真实结果显示两种写法在遍历结果上完全一样——这正是这类资源泄漏最危险的地方:程序表现得完全正常,不关闭 Stream 不会让这次调用本身出任何问题,泄漏是真实发生但当下完全不可见的。
与其猜测"应该会泄漏",不如用系统工具真实统计当前进程打开的文件描述符数量,量化整个过程。
FilesWalkResourceLeakDemo.java static int countOpenFds(long pid) throws Exception { ProcessBuilder pb = new ProcessBuilder("bash", "-c", "lsof -p " + pid + " | wc -l"); ... } for (int i = 0; i < 300; i++) { Stream<Path> s = Files.walk(root); s.findFirst(); // 真实打开目录句柄,但不消费完、不关闭 leakedStreams.add(s); }
第一次运行: 真实统计:循环开始前,当前进程真实打开的文件描述符数: 27 真实统计:300 轮不关闭的Files.walk()调用后,文件描述符数真实变为: 327(真实增加了 300 个) 真实统计:手动关闭全部Stream后,文件描述符数真实恢复为: 27 第二次运行: 循环前: 27 → 300轮后: 327(+300)→ 关闭后: 27
正确关闭版本真实完成 300 轮调用,无异常
真实结果证明用 try-with-resources 包裹 Files.walk() 调用,能够真实、可靠地在每次使用完毕后立即释放底层句柄,重复 300 轮同样的操作不会有任何异常或资源累积——这也是 JDK 文档里"必须用try-with-resources包裹Files.walk()"这条建议在真实数据支撑下最直接的证明。
每一格都是真实调用 lsof -p <pid> 统计出来的数字,不是估算的。为了方便逐帧展示,这里用 6 轮复现同样的 1:1 关系,原文 300 轮的完整实验结论完全一致。