「Java 有 GC 就不會內存洩漏」是錯誤認知。內存洩漏 = 對象可達但不再使用。GC 僅回收不可達對象,若程序持有過期引用 (Obsolete Reference),對象永不可達,導致內存耗盡。
public class Stack {
private Object[] elements;
private int size = 0;
public void push(Object e) { elements[size++] = e; }
public Object pop() {
if (size == 0) throw new EmptyStackException();
return elements[--size]; // 過期引用殘留在 elements[size] 中!
}
}
彈出元素後,數組該位置仍持有引用,GC 無法回收,隨著反覆 push/pop 內存無限增長。
public Object pop() {
if (size == 0) throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // 手動清空過期引用
return result;
}
| 場景 | 原因 | 對策 |
|---|---|---|
| 靜態集合 (HashMap/Vector) 累積對象 | 靜態變量生命周期 = 應用生命周期,元素無法釋放 | 及時 remove() 或置空集合引用
|
| 監聽器/回調未註銷 | 註冊 addListener 後未 removeListener,源對象持有監聽器引用 | 生命周期結束時顯式移除,或使用弱引用 |
| 連接池資源未關閉 | Connection/Statement/ResultSet 未 close,物理連接泄漏 | try-with-resources 或 finally 塊確保關閉 |
| ThreadLocal 未清理 | 線程池線程復用,ThreadLocal 值隨線程存活 | 使用完畢調用 remove()
|
| 緩存無過期策略 | 無界緩存持續增長 | 使用 Guava Cache / Caffeine 設置最大容量與過期時間 |
nullWeakReference / WeakHashMap消除過期引用是 Java 內存管理的核心技能。GC 不是萬能藥,開發者必須對對象生命周期負責,在恰當時機切斷引用鏈,讓 GC 高效工作。