try-with-resources资源管理
IO 资源通常对应操作系统资源,例如文件描述符、Socket 连接、缓冲区等。如果不及时关闭,可能导致文件无法删除、连接耗尽、进程打开文件数超限等线上问题。
# 1. 为什么必须关闭资源
Java 对象由 GC 管理,但底层 IO 资源不应该依赖 GC 间接释放。
例如 FileInputStream 背后可能持有文件描述符。如果大量文件流没有关闭,可能出现:
Too many open files
这是典型的资源泄漏问题。
# 2. 传统 finally 写法
早期代码常用 finally 关闭资源:
InputStream input = null;
try {
input = new FileInputStream("data.txt");
// read data
} finally {
if (input != null) {
input.close();
}
}
如果有多个资源,代码会变得冗长,而且异常处理容易写错。
# 3. try-with-resources
Java 7 引入了 try-with-resources。
try (InputStream input = new FileInputStream("data.txt")) {
// read data
}
只要资源实现了 AutoCloseable 或 Closeable,就可以放在 try 括号中,执行结束后自动关闭。
# 4. 多资源关闭顺序
try (InputStream input = new FileInputStream("source.txt");
OutputStream output = new FileOutputStream("target.txt")) {
input.transferTo(output);
}
多个资源会按照声明的反向顺序关闭。
先关闭 output
再关闭 input
这符合包装流的使用习惯:通常外层资源先关闭,底层资源后关闭。
# 5. 异常抑制
如果 try 块中发生异常,关闭资源时也发生异常,后者会作为 suppressed exception 附加到主异常上。
catch (IOException e) {
for (Throwable suppressed : e.getSuppressed()) {
suppressed.printStackTrace();
}
}
这比传统 finally 更可靠,因为 finally 中的关闭异常可能覆盖原始异常。
# 6. 包装流关闭
通常只需要把最外层流放入 try。
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream("data.txt"), StandardCharsets.UTF_8))) {
// read
}
关闭 BufferedReader 会继续关闭 InputStreamReader 和 FileInputStream。
# 7. 不应该关闭的资源
并不是所有拿到的流都应该由当前方法关闭。例如:
- Servlet 容器管理的
request.getInputStream()。 - 框架传入的响应流。
- 调用方要求继续使用的流。
谁创建资源,谁负责关闭,这是常见原则。方法如果接收外部传入的流,要在接口契约中明确是否关闭。
# 专家实践清单
| 检查项 | 建议 | 原因 |
|---|---|---|
| 资源创建 | 放在 try 资源声明中 | 自动关闭 |
| 关闭顺序 | 后创建先关闭 | 符合包装流依赖关系 |
| 异常保留 | 查看 suppressed 异常 | 避免关闭异常覆盖主异常 |
| 所有权 | 谁创建谁关闭 | 避免误关外部流 |
| 接口契约 | 明确是否关闭传入流 | 防止调用方误用 |
关闭链路:
try 块执行
│
├─ 正常完成
└─ 抛出业务异常
▼
自动按逆序 close
▼
保留主异常和 suppressed 异常
# Tips 快问快答
Q:try-with-resources 解决什么问题?
A:确保实现 AutoCloseable 的资源在离开作用域时自动关闭。
Q:多个资源关闭顺序是什么? A:按声明的逆序关闭。
Q:关闭时也抛异常怎么办? A:主异常保留,关闭异常通常作为 suppressed 异常附加。
Q:所有资源都该在方法里关闭吗? A:不一定,只有方法创建并拥有的资源才应由方法关闭。
Q:为什么不能依赖 GC 关闭文件? A:GC 时机不确定,外部资源可能长期占用。
Q:包装流只关闭最外层可以吗? A:通常可以,关闭会向内传递。
Q:AutoCloseable 和 Closeable 有什么关系?
A:Closeable 继承自 AutoCloseable,更偏 IO 资源。
Q:资源关闭最常见线上问题是什么? A:文件描述符或连接泄漏,最终导致打开文件过多或连接耗尽。
# 8. 总结
IO 代码中资源关闭是可靠性的底线。新代码应默认使用 try-with-resources,并明确资源所有权。不要依赖 GC,不要吞掉关闭异常,不要随意关闭不属于自己的流。