許多 Java 開發者對 finalize() 存在誤解,以為它是 C++ 解構函數的對應物,可用於釋放資源。實則不然。本文將從 JVM 垃圾回收原理、finalize 執行機制、對象復活現象、現代替代方案四個維度,徹底講透這個「被誤解最深」的方法。
現代 JVM(HotSpot)採用 根搜尋算法(GC Roots Tracing):
| 算法 | 核心思想 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|---|
| 標記-清除 | 標記存活,清除未標記 | 實現簡單 | 產生記憶體碎片 | 老年代 |
| 標記-整理 | 標記後向一端移動存活對象 | 無碎片 | 需移動對象、更新引用 | 老年代 |
| 複製 | 劃分兩塊區域,每次用一塊,GC 時複製存活對象到另一塊 | 無碎片、分配快 | 浪費 50% 空間 | 新生代 |
| 分代收集 | 新生代用複製,老年代用標記-整理/清除 | 綜合優勢 | 實現複雜 | 通用堆 |
protected void finalize() throws Throwable
Finalizer 守護線程異步執行,不保證及時、甚至不保證執行。
[可達] --GC 判定不可達--> [可終結] --進入 F-Queue--> [終結中] --finalize 執行完--> [已終結] --再次 GC 判定不可達--> [可回收]
↑_________最終化復活_________|
public class ResurrectionDemo {
static ResurrectionDemo obj; // GC Root 引用
@Override
protected void finalize() {
System.out.println("finalize 執行,對象復活!");
obj = this; // 重新掛載到 GC Root,對象復活
}
public static void main(String[] args) throws InterruptedException {
obj = new ResurrectionDemo();
obj = null; // 斷開引用
System.gc();
Thread.sleep(1000);
System.out.println("第一次 GC 後:" + (obj != null ? "存活" : "已回收"));
obj = null; // 再次斷開
System.gc();
Thread.sleep(1000);
System.out.println("第二次 GC 後:" + (obj != null ? "存活" : "已回收")); // 不會再執行 finalize
}
}
輸出:
finalize 執行,對象復活!
第一次 GC 後:存活
第二次 GC 後:已回收 ← finalize 不再執行,直接回收
@Deprecated(since="9", forRemoval=true),未來版本可能移除。// 自動實作 AutoCloseable,離開作用域自動 close()
try (FileInputStream fis = new FileInputStream("data.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
// 編譯器自動生成 finally 區塊調用 close()
為資源類提供 close() 方法,調用方在 finally 區塊確保釋放:
public class DbConnection implements AutoCloseable {
private Connection conn;
public void close() {
if (conn != null) {
try { conn.close(); } catch (SQLException ignore) {}
}
}
}
// 使用
DbConnection db = new DbConnection();
try {
// 業務操作
} finally {
db.close(); // 確保釋放
}
import java.lang.ref.Cleaner;
public class ResourceHandler implements AutoCloseable {
private static final Cleaner cleaner = Cleaner.create();
private final Cleaner.Cleanable cleanable;
private final Resource resource; // 外部資源
public ResourceHandler(Resource res) {
this.resource = res;
// 註冊清理動作,當對象變為 phantom reachable 時執行
this.cleanable = cleaner.register(this, () -> {
resource.release(); // 安全釋放資源
});
}
@Override
public void close() {
cleanable.clean(); // 手動觸發清理
}
}
PhantomReference,不阻塞 GC,執行更可預測。若需實現「記憶體緊張時自動清理」的緩存,請用 WeakHashMap 或 Guava Cache/Caffeine,而非 finalize。
finalize() 是歷史包袱,不要在新代碼中使用。close() 模式。