资源未关闭是Java内存泄漏最常见源头,因JVM不回收操作系统资源且某些资源类持有大量堆内存;应优先用try-with-resources确保close执行,注意close可靠性及特殊资源额外处理。

Java中资源对象没关好,是内存泄漏最常见、也最容易被忽视的源头。关键不在于“要不要关”,而在于“什么时候关、怎么关才真正生效”。
资源未关闭为什么会导致内存泄漏
FileInputStream、Socket、Connection 这类对象底层都关联着操作系统资源(文件句柄、网络端口、数据库连接等)。JVM 的垃圾回收只管堆内对象,不管这些外部资源。即使对象被回收,底层资源可能仍被占用——而更严重的是,某些资源类(如老版本 JDBC 的 Connection)内部还持有大量堆内存(缓冲区、元数据),不 close 就不会释放,直接拖垮 JVM 堆。
- 流对象(InputStream/OutputStream)未关闭 → 文件句柄泄漏 + 内部缓冲区长期驻留
- Socket 未 shutdown + close → 端口无法释放,连接状态残留,可能触发 TIME_WAIT 堆积
- 数据库连接未 close → 连接池耗尽,同时 Connection 对象自身引用的 Statement、ResultSet、网络缓冲区均无法回收
关闭逻辑必须覆盖所有执行路径
try-catch-finally 是基础,但容易漏掉异常分支或 return 语句前的清理。最稳妥的方式是把资源声明在 try-with-resources 语句头里,JVM 保证无论正常结束还是抛异常,都会调用其 close() 方法(前提是该类实现了 AutoCloseable)。
- 优先用 try-with-resources:自动调用 close(),且按声明逆序关闭,适合多资源嵌套场景
- 不用 try-with-resources 时,务必在 finally 块中 close,并对 close() 调用做 null 判断和异常捕获(避免因 close 抛异常掩盖主逻辑异常)
- 绝不把 close() 放在 try 块末尾——一旦前面代码 throw 异常,close 就跳过了
注意 close() 本身的可靠性
不是所有 close() 都真正释放资源。有些库实现存在缺陷:比如某些旧版 JDBC 驱动的 close() 只是标记连接“可复用”,实际未归还连接池;又比如自定义 Socket 包装类忘了调用底层 socket.close()。
立即学习“Java免费学习笔记(深入)”;
- 确认所用 SDK 版本中 close() 方法是否已修复已知 bug(查 release note 或 issue tracker)
- 对关键资源(如 DB 连接),可在 close 后加简单验证:比如检查连接池当前活跃数是否下降
- 避免在 close() 后继续使用该资源对象,部分实现 close 后再调用 read/write 会抛 ClosedChannelException,但也有静默失败的情况
特殊资源需额外处理
有些资源不能只靠 close(),还需配合其他操作才能彻底释放:
- Socket:建议先调用 shutdownInput()/shutdownOutput(),再 close(),确保双向通道明确终止
- SSL/TLS Socket:close 前最好显式调用 SSLSocket.getSession().invalidate(),清除会话缓存
- MappedByteBuffer:它映射的是堆外内存,不受 GC 管理,必须通过 Cleaner 或反射调用 unsafe.unmap()(Java 14+ 推荐用 FileChannel.map() 返回的 Buffer 自带 clean 机制)


















