Apache SeaTunnel 项目中的 HiveSink 组件曾存在一个典型的资源泄漏隐患。修复前后的代码对比如下所示:
修改前:
@Overridepublic List<FileAggregatedCommitInfo> commit(...) throws IOException { HiveMetaStoreProxy hiveMetaStore = HiveMetaStoreProxy.getInstance(pluginConfig); List<FileAggregatedCommitInfo> errorCommitInfos = super.commit(aggregatedCommitInfos); if (errorCommitInfos.isEmpty()) { // 处理分区逻辑... } hiveMetaStore.close(); // 如果前面出现异常,这行代码不会执行 return errorCommitInfos;}
修改后:
@Overridepublic List<FileAggregatedCommitInfo> commit(...) throws IOException { List<FileAggregatedCommitInfo> errorCommitInfos = super.commit(aggregatedCommitInfos); HiveMetaStoreProxy hiveMetaStore = HiveMetaStoreProxy.getInstance(pluginConfig); try { if (errorCommitInfos.isEmpty()) { // 处理分区逻辑... } } finally { hiveMetaStore.close(); // 保证资源一定会被释放 } return errorCommitInfos;}
这个看似简单的修改,却能有效防止资源泄漏,保证系统稳定性。接下来,让我们探讨 Java 资源管理的通用方法。
资源泄漏是指程序获取资源后没有正确释放,导致资源长期被占用。常见的需要管理的资源包括:
文件句柄
数据库连接
网络连接
线程资源
锁资源
内存资源
如果不正确管理这些资源,可能导致:
系统性能下降
内存溢出
程序崩溃
服务不可用
(1) 传统方式:try-catch-finally
Connection conn = null;try { conn = DriverManager.getConnection(url, user, password); // 使用连接} catch (SQLException e) { // 异常处理} finally { if (conn != null) { try { conn.close(); } catch (SQLException e) { // 关闭连接异常处理 } }}
缺点:
代码冗长
嵌套结构复杂
容易遗漏关闭资源
多资源时更加难以维护
(2) 现代方式:try-with-resources (Java 7+)
try (Connection conn = DriverManager.getConnection(url, user, password)) { // 使用连接} catch (SQLException e) { // 异常处理}
优点:
代码简洁清晰
自动关闭资源
即使发生异常也能正确关闭
多资源时依然保持可读性
如果需要管理自定义资源,可以实现 AutoCloseable 接口:
public class MyResource implements AutoCloseable { private final ExpensiveResource resource; public MyResource() { this.resource = acquireExpensiveResource(); } @Override public void close() { if (resource != null) { try { resource.release(); } catch (Exception e) { logger.error("关闭资源时出错", e); } } }}
实现要点:
close() 方法应该是幂等的
应处理内部异常而不是向外传播
记录资源释放失败的日志
(1) 循环中的资源管理
错误示例:
public void processFiles(List<String> filePaths) throws IOException { for (String path : filePaths) { FileInputStream fis = new FileInputStream(path); // 潜在泄漏 // 处理文件 fis.close(); // 如果处理过程抛出异常,这行不会执行 }}
正确示例:
public void processFiles(List<String> filePaths) throws IOException { for (String path : filePaths) { try (FileInputStream fis = new FileInputStream(path)) { // 处理文件 } // 自动关闭资源 }}
(2) 嵌套资源处理
// 推荐做法public void nestedResources() throws Exception { try ( OutputStream os = new FileOutputStream("file.txt"); BufferedOutputStream bos = new BufferedOutputStream(os) ) { // 使用bos } // 自动按相反顺序关闭}
(1) 数据库连接
public void processData() throws SQLException { try ( Connection conn = getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM users") ) { while (rs.next()) { // 处理数据 } } // 自动关闭所有资源}
(2) 文件复制
public void copyFile(String source, String target) throws IOException { try ( FileInputStream in = new FileInputStream(source); FileOutputStream out = new FileOutputStream(target) ) { byte[] buffer = new byte[1024]; int len; while ((len = in.read(buffer)) > 0) { out.write(buffer, 0, len); } }}
(3) 网络请求
public String fetchData(String urlString) throws IOException { URL url = new URL(urlString); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); try (BufferedReader reader = new BufferedReader( new InputStreamReader(connection.getInputStream()))) { StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { response.append(line); } return response.toString(); } finally { connection.disconnect(); }}
优先使用 try-with-resources 管理资源
如果不能使用 try-with-resources,确保在 finally 块中释放资源
自定义资源类应实现 AutoCloseable 接口
资源关闭顺序应与获取顺序相反
记得处理 close() 方法可能抛出的异常
在循环中创建资源时要特别小心