资源管理简述

98 阅读4分钟

一、什么是资源管理?

资源管理(Resource Management)就是在程序运行期间,对“有限且昂贵”的系统资源进行获取(Acquire)、使用(Use)、释放(Release)的全过程控制,目的是防止泄漏、耗尽、竞争,保证系统稳定、高效、可预测。

资源包括了操作系统层的文件描述符、套接字(Socket)、线程、内存页,JVM 层的堆内存、元空间、直接内存(DirectBuffer),框架层的数据库连接(Connection)、HTTP 连接(HttpClient)、消息队列会话、缓存、事务句柄。

数据库连接、文件句柄(FileInputStream)、网络套接字、HTTP 连接,这些的底层都对应着操作系统里的文件描述符 / 套接字 / 线程等。操作系统给每个进程的数量是有上限的(Linux 默认 1024,Windows 也有类似限制)。如果你 open() 了却忘记 close(),这个句柄就会一直被标记为“使用中”,进程级别的计数器不会归零;当达到上限时,新的连接 / 文件就打不开了,抛出 Too many open filesConnection pool exhaustedSocketException: Too many open files 等错误。

二、资源管理的三大核心问题

  1. 泄漏(Leak):打开后永远不关,导致句柄 / 内存 / 连接被占光;
  2. 过早关闭(Premature Release):还在用时就被关掉,出现 Socket closedConnection closed 异常;
  3. 竞争 / 死锁(Race / Deadlock):多个线程同时申请 / 释放顺序不一致,互相卡住。

耗尽本身不是资源管理的核心问题,而是核心问题的最终现象。

三、Java给出的通用模式

1、try-with-resources

作为 Java 7 引入的语法糖,try-with-resources 用来自动关闭实现了 java.lang.AutoCloseable 接口的资源,仅需把 open() 写在 try() 括号里,编译器就会帮你把 close() 写到 finally 里,无论代码是否抛异常,都会保证资源一定被释放。

因此,任何 new 出来的流 / 连接 / 套接字,都应当放到 try-with-resources 里;但如果是 Spring 封装好的(JdbcTemplate@Transactional 方法),就不要自己关,让框架处理。

try (资源1; 资源2; …) {
    // 使用资源
} // 自动调用 close()

括号里可以声明多个资源,用分号隔开,声明的变量必须是 AutoCloseable 或其子接口 java.io.Closeable 的实现类(文件流、Socket、JDBC 连接等),资源的作用域仅限于 try 块。

2、框架托管

Spring 的 JdbcTemplate、JPA、声明式事务,帮你借 / 还数据库连接,你只需要写业务 SQL,永远不要自己去 close()

@Transactional
public void pay(Long from, Long to, BigDecimal amt) {
    jdbcTemplate.update("update account set balance=balance-? where id=?", amt, from);
    jdbcTemplate.update("update account set balance=balance+? where id=?", amt, to);
}

:事务开启,即 @Transactional 方法入口、或 JPA EntityManager.persist() 等需要 SQL 时,连接池(HikariCP / Druid)把一条空闲连接标记为“使用中”,交给你,你看到的只是一个“逻辑连接”对象(ConnectionHolder / EntityManager),底层真正的 TCP 连接来自连接池,你没有 getConnection()close()commit()rollback()

:框架每次都会在 jdbcTemplate.update() 里自动把连接传给 JDBC 驱动,执行完 SQL 后把连接仍留在当前线程的事务上下文里(不会归还池子)。方法正常结束时,Spring 事务拦截器帮你 commit,然后把连接标记为“空闲”,归还连接池;方法抛异常时,拦截器帮你 rollback,同样会归还连接。整个 @Transactional 边界之外,连接对你是不可见的,因此你无需也不能手动关闭。

3、池化(Pooling)

HikariCP、Druid 和 Apache Commons Pool 三者都遵循同一个思路———“池化 = 限流 + 复用”,把创建 / 销毁成本很高的对象(数据库连接、线程、Socket 等)放进一个池子里统一管理,既限制最大使用量防止系统被打爆,又反复使用已有对象,减少频繁创建 / 销毁的开销。

4、try-finally(手动归还)

释放资源这类“必须执行”的操作,必须放在 finally 里,无论 try 块是否抛异常、是否提前 return,这段代码都一定会被执行;如果写在 try-catch 之后,只有 try 块正常结束才会执行,一旦中途提前 return / 抛异常就会导致资源泄漏。

四、资源管理≠垃圾回收

GC(Garbage Collection,垃圾回收)只负责“内存”自动回收。文件句柄、Socket、线程、数据库连接不是 GC 的直接职责,必须显式或框架式地 close / return,因此“内存没泄漏”不代表“资源没泄漏”。