Java 的自動垃圾回收(GC)機制讓開發者免於手動管理記憶體,但這並不代表記憶體洩漏不存在。相反,Java 中的記憶體洩漏往往更隱蔽、更難排查。本文深入剖析洩漏本質、常見成因、檢測工具與預防最佳實踐。
定義:對象不再被應用程序使用,但因仍被 GC Roots 直接或間接引用,導致垃圾回收器無法回收,佔用寶貴堆內存。
關鍵區別:
記憶體洩漏的本質就是「未使用但被引用」的對象持續累積。
public class CacheManager {
// 靜態 Map,生命周期 = 應用生命周期
private static final Map<String, Object> cache = new HashMap<>();
public void put(String key, Object value) {
cache.put(key, value); // 若不清理,value 永不被回收
}
}
若持續 put 且無移除機制,cache 持有的對象永遠不可回收。
public class EventSource {
private List<Listener> listeners = new ArrayList<>();
public void register(Listener l) { listeners.add(l); }
// 缺少 unregister() 方法!
}
Listener 實現類往往持有外部資源引用,若不註銷,整個對象圖無法回收。
ThreadLocal 變量若不手動 remove(),在線程池場景下會隨線程復用而永久駐留。
public class Outer {
private byte[] bigData = new byte[10 * 1024 * 1024]; // 10MB
public Runnable getTask() {
return new Runnable() { // 匿名內部類隱式持有 this
public void run() { /* ... */ }
};
}
}
若 Runnable 被長期持有(如提交到執行緒池),Outer 及其 10MB 數據無法回收。
| 工具 | 用途 | 關鍵功能 |
|---|---|---|
| VisualVM / JConsole | 實時監控 | 堆內存趨勢、GC 頻率、類統計 |
| Eclipse MAT | 堆轉儲分析 | 支配者樹、洩漏嫌疑報告、對象保留路徑 |
| Arthas / async-profiler | 線上診斷 | 無需重啟、火焰圖、類加載統計 |
| JFR (Java Flight Recorder) | 生產級分析 | 低開銷、事件記錄、長期趨勢 |
jmap -dump:live,format=b,file=heap.hprof <pid>WeakHashMapAutoCloseable 配合 try-with-resourcesremove(),考慮封裝工具類AutoCloseablefinalize() 不可靠且延遲 GC,改用 Cleaner 或 try-with-resourcesAssertJ 或自定義斷言檢測對象是否被回收-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path 自動保留現場Old Gen 使用率持續增長、Full GC 頻率上升、GC 後內存未下降Java 記憶體洩漏本質是「引用未及時釋放」。理解 GC Root 可達性分析原理,養成良好資源管理習慣,配合工具定期巡檢,是避免洩漏的根本之道。記住:代碼寫得漂亮不如資源釋放得乾淨。