定義:對象在應用中不再被使用,但因被其他對象持有引用,導致 GC 無法回收,佔用寶貴堆內存,最終引發 OutOfMemoryError。
關鍵區別:
對象 A (生命周期 t1-t4) ──引用──→ 對象 B (生命周期 t2-t3)
↑
└─── 當 B 業務結束 (t3),A 仍持有引用 → B 無法回收
若 A 類對象大量存在(如靜態集合、單例、線程局部變量),將累積海量不可回收對象,撐爆堆內存。
若 B 還持有大量其他對象引用(C、D、E...),整個對象圖全部「被拖累」無法回收,放大洩漏影響。
| 場景 | 典型代碼模式 | 預防措施 |
|---|---|---|
| 靜態集合 | static Map<K,V> cache = new HashMap<>(); 持續 put 無 remove | 用 WeakHashMap、Guava Cache、Ehcache 等帶過期淘汰策略的容器 |
| 監聽器/回調未移除 | eventBus.register(listener); 無對應 unregister() | 實現 DisposableBean 或 @PreDestroy 統一註銷;用弱引用監聽器 |
| 自定義數據結構 | 自實現 Stack/Pool/緩存,元素出棧/歸還後未置 null | 手動將過期槽位設為 null;優先用成熟庫 |
| 內部類持有外部類引用 | 非靜態內部類實例隱式持有 Outer.this | 改為 static 內部類 + 弱引用持有外部類;或用 Lambda 替代 |
| ThreadLocal 未清理 | 線程池場景下 ThreadLocal 值隨線程長期存活 | 用完立即 remove();配合線程池重寫 ThreadFactory 清理 |
| 連接/流資源未關閉 | JDBC Connection、IO Stream、Socket 無 finally 關閉 | Try-with-resources (JDK 7+) 強制自動關閉 |
substring() 為何洩漏?public String substring(int begin, int end) {
return new String(value, offset + begin, end - begin); // 共享底層 char[] value
}
問題:截取 1MB 字符串的前 10 字符,新對象仍引用原 1MB 數組 → 原大對象無法回收。
public String substring(int begin, int end) {
int len = end - begin;
char[] newVal = Arrays.copyOfRange(value, offset + begin, offset + begin + len);
return new String(newVal, 0, len); // 拷貝新數組,斷開引用
}
heapdump、oom 命令「誰創建,誰負責釋放;誰持有,誰負責解除引用」
remove()/clear()register/unregisterremove() 消除引用