close()必须放在finally块里,因为无论try中是否抛异常,finally都能保证执行,避免文件句柄泄漏;但需判空并捕获close()自身异常,防止npe或覆盖主异常。

为什么 close() 必须放在 finally 块里
因为 try 块中一旦抛出异常(比如 IOException),后续代码就不再执行,如果把 close() 写在 try 末尾,流大概率没关上——文件句柄泄漏、磁盘写入不完整、临时文件残留,都是这么来的。
常见错误现象:java.io.IOException: Stream closed 反复出现;程序跑几次后报 Too many open files;用 lsof -p <pid></pid> 查看发现几百个 REG 类型文件描述符未释放。
-
finally是唯一能保证「无论是否异常都执行」的结构,但要注意:它不捕获异常,只负责兜底执行 - 如果
close()自己也抛异常(比如网络流断连时),它会覆盖try中的原始异常——这是最常被忽略的坑 - JDK 7+ 推荐用
try-with-resources,但老项目或需要兼容 JDK 6 时,finally+ 手动close()仍是标准解法
close() 在 finally 中怎么写才安全
直接调 stream.close() 不行。原因很简单:流对象可能为 null(比如构造时就失败),或者 close() 自身抛出 IOException,导致吞掉原异常或引发 NullPointerException。
正确写法是判空 + try-catch 套一层:
立即学习“Java免费学习笔记(深入)”;
InputStream is = null;
try {
is = new FileInputStream("data.txt");
// ... 读取逻辑
} catch (IOException e) {
// 处理业务异常
} finally {
if (is != null) {
try {
is.close();
} catch (IOException ignored) {
// 忽略 close 异常,不干扰主流程
}
}
}
- 必须判
is != null,否则构造失败时is是null,调close()直接NullPointerException -
close()的异常要捕获并忽略(或记录 warn 日志),绝不能往上抛——它只是清理动作,不该改变主异常语义 - 多个流(如
InputStream+OutputStream)要各自独立判空关闭,顺序无关,但每个都得套自己的try-catch
为什么不能在 catch 里关流
因为 catch 只捕获对应类型的异常,而有些异常(比如 RuntimeException 或未声明的 Error)根本进不了 catch 块,流照样泄漏。
典型场景:OutOfMemoryError 发生在 try 中间;或者你只写了 catch (IOException e),但实际抛了 NullPointerException —— 这两种情况,catch 都不会执行。
-
finally是 JVM 层面保障执行的,只要线程没 kill,它就一定走 - 哪怕
try里写了return,finally也会先执行完再返回(除非System.exit()或线程被强制终止) - 别信“我只处理 IO 异常,别的异常不归我管”——资源泄漏不挑异常类型
JDK 7+ 的 try-with-resources 真的能替代 finally 吗
能,但有前提:资源类必须实现 AutoCloseable,且你得确保它的 close() 方法是幂等、无副作用、不依赖外部状态的。
常见陷阱:
- 自定义流类没重写
close(),或重写了但没调父类super.close(),导致底层资源没真正释放 - 多个资源按声明顺序逆序关闭,如果前一个
close()抛异常,后面的就不会关了(JDK 7 默认行为) - 某些老库的流(如早期 Apache Commons IO 的
CloseableIterator)实现不规范,try-with-resources关不干净
所以,即使用了 try-with-resources,上线前仍建议用 lsof 或 jstack + jmap 验证句柄数是否稳定。
真正难处理的,是那些没实现 AutoCloseable 却持有系统资源的对象——比如 JNI 分配的内存、自定义 socket 封装、数据库连接池外的手动 Socket,它们只能靠 finally 手动收尾。










