为何JVM无法对未关闭的连接及资源进行垃圾回收?
JVM无法自动回收未关闭资源的核心原因
JVM垃圾回收(GC)的作用边界非常明确:仅回收JVM堆内存中无任何强引用关联的Java对象,完全不干涉操作系统内核层面分配的资源。未关闭的连接/资源无法被自动回收,本质是两个层面的问题:
- 未关闭资源对应的Java包装对象,通常被静态集合、长生命周期容器、连接池等对象持有强引用,GC不会判定为可回收对象
- 即使Java对象被回收,操作系统层面分配的文件句柄、TCP会话、数据库会话、本地内存等内核资源,也不会被GC自动释放,必须调用对应
close()方法主动通知内核回收
示例1:未关闭的文件流泄漏
public class FileLeakDemo { // 长生命周期的静态集合,默认持有所有打开的流对象强引用 private static final List<InputStream> OPEN_STREAMS = new ArrayList<>(); public void readFile(String path) throws IOException { FileInputStream fis = new FileInputStream(path); OPEN_STREAMS.add(fis); // 仅读取内容,未移除集合引用,也未调用fis.close() byte[] buffer = new byte[1024]; fis.read(buffer); } }
上述代码中,FileInputStream对象被静态集合持有强引用,程序运行全程不会被GC回收。同时每个FileInputStream对应操作系统分配的1个文件句柄,就算手动把对象从集合移除触发GC回收,只要没有调用close(),该文件句柄依然会被操作系统标记为占用,直到进程退出才会释放,累积到系统上限就会抛出Too many open files错误。
示例2:未关闭的数据库连接泄漏
public class DbConnectionLeakDemo { public void queryUser() throws SQLException { // 从连接池获取数据库连接 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456"); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("select * from user"); // 处理完结果直接返回,未关闭rs、stmt、conn while (rs.next()) { System.out.println(rs.getString("name")); } } }
这里的Connection对象底层对应数据库服务端的1个会话连接,以及操作系统层面的TCP连接。未调用close()的情况下,连接对象会被连接池内部持有强引用,GC不会回收,数据库服务端的会话也会一直保持活跃,占满连接池上限后新的请求就无法获取连接,抛出连接耗尽异常。
兜底机制说明
部分JDK自带的资源类(如FileInputStream、ByteBuffer)实现了Cleaner虚引用机制,当Java对象被GC回收时,会触发兜底逻辑释放关联的内核资源。但该机制完全依赖GC的触发时机,不可控性极高,如果资源分配速度远快于GC触发频率,依然会出现资源耗尽问题,绝对不能替代手动close()操作。
内容的提问来源于stack exchange,提问作者Sachin Rane
相关产品推荐
相关产品推荐

