GC 將堆中對象分為 存活對象 (被引用) 與 垃圾對象 (不可達)。核心任務:識別垃圾 → 回收空間 → 供再分配。算法選擇與參數調優直接決定應用吞吐與停頓表現。
OutOfMemoryError。不可預測性:JVM 根據動態環境決定觸發時機,長期運行應用 Major GC 必然反覆發生。
| 原則 | 具體做法 | 原理 |
|---|---|---|
| 避免顯式 System.gc() | 刪除代碼中的調用 | 僅是建議,可能觸發 Major GC,增加停頓頻率 |
| 減少臨時對象 | 複用對象、使用基本類型、避免循環 new | 減少 Young GC 壓力,延緩 Major GC 觸發 |
| 對象不用置 null | 集合 remove 後、引用超出作用域前 | 幫助 GC 更快識別垃圾,提高回收效率 |
| 字符串拼接用 StringBuilder | 替換 + / String.concat | 避免產生大量中間 String 對象 |
| 優先基本類型 | int 而非 Integer | 基本類型無對象頭,內存佔用極小,無 GC 壓力 |
| 慎用靜態字段 | 緩存、單例改用弱引用或定期清理 | 靜態字段 GC Root 強引用,生命周期 = 類加載器,易洩漏 |
| 分散對象創建/銷毀 | 批量操作改為流式/分批處理 | 避免瞬間分配壓力觸發強制 Major GC |
protected void finalize() throws Throwable:對象被回收前由 GC 線程調用 一次。try-with-resources / AutoCloseable / PhantomReference + ReferenceQueue (需精確控制釋放時機)。Java 所謂「內存洩漏」實為:開發者以為應釋放的對象,因編碼疏忽仍被 GC Root 可達,導致無法回收。
// 1. 集合元素未清理 (栈實現缺陷)
public class ObjStack {
private Object[] stack = new Object[64];
private int top = 0;
public void push(Object o) { stack[top++] = o; }
public Object pop() { return stack[--top]; } // BUG: stack[top] 仍持有引用!
// 修正: Object obj = stack[--top]; stack[top] = null; return obj;
}
// 2. 靜態集合緩存無上限
static final Map CACHE = new HashMap<>(); // 持續增長 → OOM
// 修正: 使用 LinkedHashMap + removeEldestEntry 或 Guava Cache / Caffeine
// 3. 監聽器/回調未移除
button.addListener(listener); // 組件銷毀時未 removeListener → listener 及其閉包對象洩漏
// 4. 線程局部變量 (ThreadLocal) 未清理
static ThreadLocal TL = new ThreadLocal<>();
// 線程池線程復用時,若不 remove(),Connection 永不釋放
SoftReference 值 + ReferenceQueue 清理,內存緊張時自動回收。// 改進算法:固定容量 LRU
Map cache = new LinkedHashMap(128, 0.75f, true) {
@Override protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > MAX_SIZE;
}
};
Class 對象、靜態字段、常量池;若應用卸載時 ClassLoader 不可達 → 整個類空間洩漏。-Xlog:gc*:觀察 Old Gen 使用率是否階梯式上升。jmap -dump:live,format=b,file=heap.hprof <pid> 獲取堆快照。Histogram 找大對象 → Merge Shortest Paths to GC Roots 定位持有鏈 → 定位代碼行。finalize(),擁抱 AutoCloseable 與弱/軟引用。