Java 自動垃圾回收 (GC) 只負責回收不可達物件。若物件仍被引用但不再使用,GC 無法回收,即構成 Java 記憶體洩漏。長期運行的伺服器程式 (JSP/Servlet/EJB) 與嵌入式系統最受影響。
優點:自動處理循環引用 (A→B→A 但無根引用即可回收)。缺點:遍歷開銷大、GC 停頓不可控。
滿足雙重條件的物件:
對比 C++:C++ 洩漏包含「不可達但未釋放」;Java 語言層面保證不可達必被 GC 回收,洩漏範圍較窄。
Vector v = new Vector(10);
for (int i = 0; i < 100; i++) {
Object o = new Object();
v.add(o);
o = null; // 僅切斷區域變數引用,v 仍持有 o
}
// 此時 100 個 Object 皆可達 (v → o),無法被 GC 回收
修正:v.clear() 或 v = null 切斷根引用鏈。
| 模式 | 說明 | 對策 |
|---|---|---|
| 集合類持有過期物件 | List/Map/Set 長期存活,持續 add 未 remove | 定期清理、使用 WeakHashMap、設定容量上限 |
| 靜態集合/快取 | static Map 做快取無淘汰策略 | 採用 Ehcache/Guava Cache 等支援 TTL/LRU 的快取 |
| 監聽器/回調未移除 | addListener 無對應 removeListener | 生命週期綁定、使用 WeakReference 包裝監聽器 |
| ThreadLocal 未清理 | 執行緒池重用導致 ThreadLocal 殘留 | 用完即 remove()、框架層面統一清理 |
| 資源未關閉 | Connection/Stream/ResultSet 未 close | try-with-resources (JDK 7+)、finally 區塊 |
| 內部類持有外部類引用 | 非靜態內部類實例隱含 this 參考 | 改為 static nested class、或弱引用 |
核心原理:Instrumentation API 修改 bytecode,記錄物件分配/釋放/引用變化,構建堆快照分析可達性。
| 工具 | 特色 | 適用場景 |
|---|---|---|
| VisualVM (內建) | 免費、整合 jstat/jmap/jhat、支援 Heap Dump 分析 | 日常開發、快速定位 |
| JProfiler | 商業、UI 豐富、CPU/記憶體/執行緒三合一、遠端分析 | 生產環境深度調優 |
| YourKit | 商業、低開銷採樣、強大的物件保留路徑分析 | 高併發系統線上診斷 |
| Eclipse MAT | 免費、專精 Heap Dump 分析、支援 OQL 查詢、支援超大堆 | 事後離線分析、找出支配者樹 |
| Arthas/Async-Profiler | 線上無侵入、火焰圖、類載入監控 | 生產環境不可重啟時 |
現代工具 (MAT/JProfiler) 均提供同等或更強功能。
jmap -dump:live,format=b,file=heap.hprof <pid>)。-XX:+UseG1GC 或 -XX:+UseZGC (JDK 11+),設定 -XX:MaxGCPauseMillis。System.gc() 僅建議,JVM 可忽略。Java 記憶體洩漏本質是邏輯引用殘留。預防勝於治療:代碼審查關注集合/快取/監聽器/ThreadLocal 生命週期,上線前必跑壓測 + Heap Dump 基線分析。