GC 只管記憶體(堆上物件)。檔案控制代碼、Socket、資料庫連線、鎖、訊號量等非記憶體資源必須顯式釋放,否則會導致資源耗盡、效能驟降甚至服務癱瘓。
// ❌ 若 props.load() 拋出異常,stream 永不關閉
public static Properties loadBadly(String file) throws IOException {
FileInputStream stream = new FileInputStream(file);
Properties props = new Properties();
props.load(stream);
stream.close(); // 異常時執行不到
return props;
}
// ✅ try-finally 確保釋放
public static Properties loadCorrectly(String file) throws IOException {
FileInputStream stream = new FileInputStream(file);
try {
Properties props = new Properties();
props.load(stream);
return props;
} finally {
stream.close(); // 無論正常或異常都會執行
}
}
關鍵: 資源取得放在 try 區塊外部,避免取得失敗進入 finally 導致 NullPointerException 掩蓋原始異常。
// ❌ 繁瑣、易漏、異常遮蔽風險高
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
// 處理 rs...
} finally {
if (rs != null) try { rs.close(); } catch (SQLException ignore) {}
if (stmt != null) try { stmt.close(); } catch (SQLException ignore) {}
if (conn != null) try { conn.close(); } catch (SQLException ignore) {}
}
// ✅ 自動按宣告逆序關閉、異常鏈保留、程式碼極簡
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
// 處理 rs...
}
AutoCloseable 的類別皆可用。rs → stmt → conn(宣告逆序)。close() 拋出異常,會被 suppressed exception 附加在主要異常上,不遮蔽原始錯誤。Object.finalize() 為 @Deprecated(forRemoval=true),未來將移除。| 資源 | 洩漏後果 | 典型原因 |
|---|---|---|
| 資料庫連線 | 連線池耗盡、新請求掛起/失敗 | 未在 finally 歸還、長事務未提交 |
| 檔案控制代碼 | 「太多開啟檔案」、無法建立新檔 | 流未關閉、臨時檔未刪 |
| 執行緒/執行緒池 | OOM、CPU 飆升、服務無回應 | 任務未結束、關閉鉤子未註冊 |
| 鎖/訊號量 | 死鎖、飢餓、吞吐量歸零 | 未在 finally 釋放鎖 |
try-finally(舊版)。