try-with-resources 并非万能,资源泄漏主因是资源未实现 AutoCloseable、嵌套资源遗漏关闭(如 Files.lines 返回的 Stream)、异常导致提前退出或异步使用中过早关闭;需结合系统句柄监控与代码审查定位真实泄漏点。
try-with-resources 本身是防止资源泄漏的正确写法,但它不是“万能保险”。排查中发现“用了 try-with-resources 却仍有 Unclosed Resource”,说明问题不在语法错误,而在资源链未被完整覆盖、异常中断执行路径,或资源对象本身不符合 AutoCloseable 合约。
很多开发者误以为“new 了流就自动可关闭”,但关键要看构造出的对象类型:
常见陷阱是只关闭外层包装,忽略底层原始资源:
try (var reader = new BufferedReader(new FileReader("a.txt"))) { ... } —— FileReader 被关闭,但 BufferedReader 的 close() 会调用 FileReader.close(),看似安全;try (var is = Files.newInputStream(Paths.get("x.log"))) { ... } —— 正确;但若改成 try (var is = new DataInputStream(new FileInputStream("x.log"))) { ... },DataInputStream.close() 会调用 FileInputStream.close(),仍安全;Files.lines(path) 返回 Stream<String> —— 它内部打开文件流,但 Stream 不实现 AutoCloseable,必须手动 try-with-resources 包裹或显式 close(),否则句柄泄露。try-with-resources 的 close() 在 try 块退出时(无论正常还是异常)都会执行,但有两类例外:
立即学习“Java免费学习笔记(深入)”;
不依赖代码逻辑猜测,直接看操作系统是否还持有句柄:
lsof -p <pid> | grep ".log|.dat" | wc -l,观察数量是否随请求增长;-Djdk.net.URLClassPath.disableJarChecking=true 并启用 -XX:+PrintGCDetails,结合 jstack 查看阻塞在线程 IO 调用上的堆栈,往往指向未释放的 Socket 或 FileChannel。本质上,try-with-resources 是语法糖,不是魔法。它只保证“该对象的 close() 被调用”,不保证 close() 内部逻辑一定释放了所有底层资源。定位 Unclosed Resource,核心是把“谁没关”变成“哪个 fd 没退”,再倒查是哪个对象创建了它却没彻底清理。