Java 的垃圾回收機制雖能自動管理記憶體,但若物件生命週期管理不當,仍會導致記憶體洩漏。常見形式之一是物件逗留:本應短暫存在的物件被長期強參考持有,無法被回收。本文介紹 java.lang.ref.SoftReference(軟參考)如何協助垃圾收集器依記憶體壓力彈性回收物件,並示範修正洩漏程式碼與設計低成本快取的實務技巧。
以下 LeakyChecksum 類別為了「效能」將位元組陣列緩存為實例欄位:
// BAD CODE - DO NOT EMULATE
public class LeakyChecksum {
private byte[] byteArray; // 強參考,永不釋放
public synchronized int getFileChecksum(String fileName) {
int len = getFileSize(fileName);
if (byteArray == null || byteArray.length < len)
byteArray = new byte[len]; // 只增不減
readFileContents(fileName, byteArray);
// 計算 checksum...
}
}
後果:緩衝區永遠不會被 GC 回收,且隨最大檔案增長而膨脹,造成記憶體壓力。
OutOfMemoryError 前)才回收。import java.lang.ref.SoftReference;
public class CachingChecksum {
private SoftReference bufferRef; // 軟參考
public synchronized int getFileChecksum(String fileName) {
int len = getFileSize(fileName);
byte[] byteArray = bufferRef.get(); // 取得物件或 null
if (byteArray == null || byteArray.length < len) {
byteArray = new byte[len];
bufferRef = new SoftReference<>(byteArray); // 重新包裝
}
readFileContents(fileName, byteArray);
// 計算 checksum...
}
}
現在緩衝區在記憶體充足時保留,不足時自動被 GC 回收,下次呼叫會重新分配。
| 策略 | 結構 | 優點 | 缺點 |
|---|---|---|---|
| 單一 Map | Map | 精細控制每個條目 | GC 需掃描每個 Reference,負擔較重 |
| 整體軟參考 | SoftReference | GC 只需處理一個 Reference,回收整個快取效率高 | 全有或全無,無法單獨淘汰條目 |
建議:若快取條目數量大、存取頻率高,優先考慮整體軟參考策略。
以陣列實作堆疊時,pop() 若不清除陣列元素,會導致彈出物件仍被強參考:
public class LeakyStack {
private Object[] elements = new Object[MAX_ELEMENTS];
private int size = 0;
public Object pop() {
if (size == 0) throw new EmptyStackException();
Object result = elements[--size];
// elements[size] = null; // 必須手動清除!
return result;
}
}
修正:在 pop() 後將 elements[size] = null。或改用連結串列實作,生命週期自動綁定。
軟參考提供「記憶體不夠再丟棄」的彈性語意,是解決物件逗留、實作低成本快取的利器。但需注意:它無法替代 完整快取框架(如 Ehcache、Caffeine)的淘汰策略、持久化、事務支援等進階功能。在簡單場景下,善用 SoftReference 能以最小代價大幅降低記憶體洩漏風險。