Java 的垃圾收集器(GC)極大地簡化了記憶體管理,但它只負責「記憶體」這一種資源。檔案控制代碼、資料庫連線、Socket、鎖、信號量等非記憶體資源,若不顯式釋放,極易導致資源耗盡、系統不穩定。本文整理自 Brian Goetz 經典專欄,系統說明何時、為何、以及如何正確進行「內務處理」。
GC 只會回收「不可觸及」物件佔用的堆內存。對於作業系統層面的有限資源(檔案描述符、連接埠、資料庫連線池配額),GC 回收時機不可預測,等待 finalizer 執行往往為時已晚。
InputStream、OutputStream、SocketConnection、Statement、ResultSetLock、Semaphore、ConditionGraphics、BufferedImageExecutorServicepublic static Properties loadBadly(String file) throws IOException {
FileInputStream in = new FileInputStream(file);
Properties p = new Properties();
p.load(in); // 若拋出異常,in 永不關閉
in.close();
return p;
}
public static Properties load(String file) throws IOException {
try (FileInputStream in = new FileInputStream(file)) {
Properties p = new Properties();
p.load(in);
return p;
} // 編譯器自動生成 finally { in.close(); }
}
優點:簡潔、異常安全、支援多資源、自動抑制異常(suppressed exception)保留原始堆疊追蹤。
FileInputStream in = new FileInputStream(file);
try {
Properties p = new Properties();
p.load(in);
return p;
} finally {
if (in != null) try { in.close(); } catch (IOException ignored) {}
}
注意:資源取得必須在 try 區塊外部,否則取得失敗時 finally 仍會執行導致 NullPointerException 遮蔽原始異常。
public void query(Connection conn, String sql) throws SQLException {
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) { /* 處理資料 */ }
} // 自動關閉 rs → stmt,conn 由呼叫端管理
}
若無法使用 try-with-resources,必須採用反序關閉、逐層 try-catch:
finally {
try { if (rs != null) rs.close(); } catch (SQLException e) { log(e); }
try { if (stmt != null) stmt.close(); } catch (SQLException e) { log(e); }
try { if (conn != null) conn.close(); } catch (SQLException e) { log(e); }
}
public class BoundedSet {
private final Set set = new HashSet<>();
private final Semaphore sem;
public BoundedSet(int bound) { sem = new Semaphore(bound); }
public boolean add(T o) throws InterruptedException {
sem.acquire();
boolean added = false;
try {
added = set.add(o);
return added;
} finally {
if (!added) sem.release(); // 釋放許可,否則永久洩漏
}
}
}
若 set.add() 拋出異常(如 hashCode() 錯誤),缺少 finally 會導致信號量永久減少,最終造成系統停擺。
ThreadPool → ThreadPool 擁有 Thread任何直接/間接持有非記憶體資源的物件,應提供 close()、shutdown()、release() 等方法,並實作 AutoCloseable 以配合 try-with-resources。
FileInputStream 的 finalizer 會在開發者忘記 close() 時嘗試關閉| 工具/技術 | 用途 |
|---|---|
| SpotBugs / FindBugs | 靜態分析偵測未關閉資源 |
| try-with-resources (Java 7+) | 編譯期保證資源釋放 |
| AutoCloseable | 自訂資源納入標準流程 |
| Cleaner API (Java 9+) | 替代 finalizer 的輕量級清理機制 |
「不走彎路,就是捷徑。」養成「取得即規劃釋放」的肌肉記憶,善用 try-with-resources 與 AutoCloseable,將資源管理納入型別系統,才能寫出健壯、可維護的 Java 系統。