Java 的自動記憶體管理是其核心優勢之一,但理解垃圾回收(GC)的特性與限制,對於編寫高效、穩定的程式至關重要。本文深入探討 GC 機制、常見記憶體洩漏場景及應對策略。
System.gc() 或 Runtime.getRuntime().gc() 建議執行finalize(),但執行時間與是否執行皆無保證採用 try-finally 或 try-with-resources 確保資源釋放:
import java.io.*;
public class ManualClear {
public static void main(String[] args) {
FileInputStream fis = null;
try {
fis = new FileInputStream("./src/ManualClear.java");
byte[] buf = new byte[1024];
int len;
while ((len = fis.read(buf)) > 0) {
System.out.println(new String(buf, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try { fis.close(); } catch (IOException ignored) {}
}
}
}
}
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_CAPACITY = 16;
public Stack() { elements = new Object[DEFAULT_CAPACITY]; }
public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0) throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // 關鍵:消除過期引用
return result;
}
private void ensureCapacity() {
if (elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
關鍵點:pop() 後必須將陣列位置設為 null,否則 GC 無法回收已彈出的物件。
解決方案:使用 WeakHashMap 或後台執行緒定期清理過期條目。
最佳實踐:顯式調用 removeListener() 或在生命週期結束時清理。
finalize()close() 的檔案)class Parent {
@Override protected void finalize() throws Throwable {
System.out.println("Parent finalize");
}
}
class Child extends Parent {
@Override protected void finalize() throws Throwable {
System.out.println("Child finalize");
// 忘記 super.finalize() 或拋出異常導致父類 finalize 不執行
}
}
class Parent {
private final Object guardian = new Object() {
@Override protected void finalize() throws Throwable {
System.out.println("父類清理邏輯必定執行");
}
};
}
即使子類 finalize() 失敗,守護對象的 finalize() 仍會被 GC 調用,確保父類清理邏輯執行。
理解 GC 的邊界與限制,配合 try-with-resources、WeakReference、監聽器註銷等實踐,才能構建健壯的 Java 應用。finalize() 僅作為最後防線,不可依賴其釋放關鍵資源。