無用對象(不再被應用邏輯引用)因被 GC Root 間接引用,導致無法被垃圾回收,長期佔用堆內存,最終引發 OutOfMemoryError。
static List<Object> cache = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
Object obj = new LargeObject();
cache.add(obj); // 靜態集合生命週期 = 類生命週期 = JVM 生命週期
obj = null; // 僅斷開局部引用,對象仍被 cache 引用
}
// 正確:用完後 cache.remove(obj) 或 cache.clear()
特徵:`static Map/List/Set` 作為緩存卻無清理策略。
Set<Person> set = new HashSet<>();
Person p = new Person("Tom", 25);
set.add(p);
p.setAge(26); // age 參與 hashCode/equals,導致 hashCode 改變
set.remove(p); // 找不到原桶位置,刪除失敗 → 內存洩漏
set.add(p); // 重複添加成功,size 增加
規則:作為 `Set` 元素或 `Map` 鍵的對象,**不可變**其參與 `hashCode/equals` 的字段。
button.addActionListener(listener);
// 後來 button 不再使用,但 listener 仍被 button 內部列表引用
// 應在銷毀時:button.removeActionListener(listener)
常見於:Swing 事件、Netty ChannelHandler、RxJava Subscription、EventBus 註冊。
| 資源 | 關閉方法 | 備註 |
|---|---|---|
| Connection | conn.close() | 必須顯式關閉,連接池歸還即調用 close |
| Statement/ResultSet | stmt.close() / rs.close() | 關閉 Connection 會級聯關閉,但連接池下需顯式關閉 |
| Socket | socket.close() | 輸入/輸出流關閉會級聯關閉 Socket |
| InputStream/OutputStream | stream.close() | try-with-resources 自動管理 |
最佳實踐:統一使用 try-with-resources (JDK 7+) 或 finally 確保關閉。
class Outer {
BigData big = new BigData();
Runnable task = new Runnable() { // 匿名內部類
public void run() { System.out.println(big.size()); }
};
}
// 若 task 被長期持有(如提交到線程池、註冊為監聽器),Outer 實例無法回收
解決:靜態內部類 + 弱引用持有外部實例,或改用 Lambda (不捕獲外部實例時)。
class Singleton {
private static Singleton INSTANCE = new Singleton();
private Map<String, Object> cache = new HashMap<>(); // 持有業務對象
public void put(String k, Object v) { cache.put(k, v); }
}
// 業務對象本應請求級別銷毀,卻被單例緩存長期持有
對策:單例僅持有無狀態服務、配置、線程池等長壽命對象;業務數據用弱引用 (WeakHashMap) 或定期清理。
jmap -dump:live,format=b,file=heap.hprof <pid> → MAT/Eclipse Memory Analyzer 定位 GC Root 鏈路。