是的,FileInputStream 的 finalize() 方法在 GC 回收前会尝试调用 close() 以释放文件描述符,但该机制不可靠、不及时、不保证执行,且自 JDK 9 起已废弃,唯一可靠方式是显式调用 close() 或使用 try-with-resources。

是的,Java 中 FileInputStream 的 finalize() 方法在 GC 回收该对象前会尝试调用 close(),从而间接关闭底层文件描述符(FileDescriptor)。但这只是“尽力而为”的安全网,并不可靠、不及时、不保证执行。
FileInputStream 的 finalize 确实会 close,但有严重限制
FileInputStream 在 JDK 8 及更早版本中重写了 protected void finalize() throws IOException,其核心逻辑是:
- 检查
fd是否非空且不是标准输入(FileDescriptor.in); - 若满足条件,则调用
close(); -
close()内部会真正释放FileDescriptor(通过fd.closeAll(...)触发 native 层关闭)。
所以从代码行为上看:GC 触发 finalize → finalize 调用 close → close 释放 fd。
但关键问题在于——这整条链路依赖于 GC 的调度,而 GC 不承诺以下任何一点:
立即学习“Java免费学习笔记(深入)”;
- 对象何时变得不可达
- GC 何时运行
-
finalize()何时被调用(甚至是否被调用) -
finalize()执行是否成功(比如抛异常可能被静默吞掉)
✅ 示例验证:自定义继承类重写
finalize()加日志,配合System.gc()可观察到调用;但去掉System.gc(),仅靠自然 GC,可能数秒、数分钟甚至永不触发。
finalize 已废弃,且 JDK 9+ 彻底转向 Cleaner 机制
-
finalize()自 Java 9 起被标记为@Deprecated,官方明确不推荐使用; - JDK 9 引入
java.lang.ref.Cleaner,FileInputStream(及FileOutputStream等)在较新 JDK 中已改用FileCleanable与Cleaner协作清理 fd; -
Cleaner基于虚引用(PhantomReference),比finalize()更轻量、可注册回调、不阻塞 GC,但仍不替代显式 close。
也就是说:
➡️ 旧版靠 finalize() 是“赌运气”;
➡️ 新版靠 Cleaner 是“稍好一点的赌运气”;
➡️ 唯一可靠方式,始终是主动调用 close()。
不关流的实际后果很直接
- 文件描述符(fd)是操作系统级资源,数量有限(Linux 默认通常 1024 或 65536);
-
FileInputStream不close()→ fd 长期占用 →Too many open files错误; - 多线程或高并发场景下,泄漏速度极快,服务可能几小时内宕机;
- 即使你“测试时没出错”,也不代表生产环境安全——GC 行为随堆大小、JVM 参数、负载强度剧烈变化。
正确做法就三条
-
使用
try-with-resources(最推荐):try (FileInputStream fis = new FileInputStream("a.txt")) { // 读取... } // 自动调用 fis.close() -
手动关闭必须放在
finally且判空:FileInputStream fis = null; try { fis = new FileInputStream("a.txt"); // ... } finally { if (fis != null) { try { fis.close(); } catch (IOException ignored) {} } } 切勿依赖 finalize / Cleaner / System.gc() 去“兜底”。
不复杂,但容易忽略。


















