現代 JVM 的垃圾收集器(GC)經過高度優化,但開發者的編碼習慣直接影響 GC 效能與記憶體洩漏風險。本文整理六大實戰準則,幫助你撰寫高效能、低延遲的 Java 應用程式。
JVM 的分代收集機制針對「朝生夕死」物件極度優化:新生代(Young Generation)採用複製演算法,存活物件極少時 GC 幾乎零開銷。相反地,物件重用(Object Pooling)增加程式複雜度,反而延長物件壽命導致提早晋升到老年代,觸發昂貴的 Major GC。
JIT 編譯器會自動分析區域變數的存活範圍(Liveness Analysis),在變數最後一次使用後即視為可回收。刻意寫 obj = null 不僅無益,反而干擾 JIT 優化。
// ❌ 不必要的手動清空
List<String> list = fetchData();
process(list);
list = null; // 無意義
// ✅ 正確做法:縮小變數作用域
void processData() {
List<String> list = fetchData(); // 區域變數,方法結束自動可回收
process(list);
}
唯一例外:陣列元素明確釋放(如 array[i] = null),但建議直接改用 ArrayList 等標準集合。
-XX:+DisableExplicitGC 可強制忽略-Dsun.rmi.dgc.client.gcInterval=3600000
-Dsun.rmi.dgc.server.gcInterval=3600000
-XX:+ExplicitGCInvokesConcurrent // 讓 System.gc() 改觸發 CMS 並發收集
| 缺點 | 說明 |
|---|---|
| 分配/回收成本高 | 需進入 Finalizer 佇列,額外 GC 週期才能真正回收 |
| 執行時機不可控 | JVM 關閉時不再執行 GC,finalize 可能永不被呼叫 |
| 順序不確定 | 相依物件釋放順序無法保證,非 C++ 解構函數替代品 |
| 例外吞噬 | finalize 拋出例外會被忽略,導致資源洩漏難以發現 |
替代方案:實作 AutoCloseable,搭配 try-with-resources 區塊確定性釋放資源。
// SoftReference:記憶體不足時才回收,適合快取
SoftReference<Bitmap> cache = new SoftReference<>(loadBitmap());
Bitmap bmp = cache.get(); // 可能回傳 null
// WeakReference:GC 即回收,適合監聽器、回呼管理
WeakReference<Listener> ref = new WeakReference<>(listener);
// ReferenceQueue:物件被回收時獲得通知,優於 finalize
ReferenceQueue<Object> queue = new ReferenceQueue<>();
WeakReference<Object> ref = new WeakReference<>(obj, queue);
WeakReference<?> cleaned = queue.poll(); // 非阻塞檢查
洩漏本質:物件對應用已無用,但仍被 GC Roots 可達(靜態集合、ThreadLocal、未移除的監聽器、內部類別隱含外部參考)。
| 常見洩漏來源 | 解決策略 |
|---|---|
| 長壽命集合持有短命物件 | 使用 WeakHashMap、定期清理、弱參考包裝 |
| 變數作用域過大 | 區域變數勿宣告為欄位、靜態欄位審慎使用 |
| 異常路徑未釋放資源 | finally 區塊 / try-with-resources 保證釋放 |
| Swing 監聽器未移除 | 元件銷毀時明確 removeListener |
| 內部類別隱含外部參考 | 靜態內部類別 + WeakReference 外部實例 |
null 賦值System.gc() 並檢查第三方庫AutoCloseable 取代 finalize()SoftReference,監聽器用 WeakReferencejmap / VisualVM 掃描洩漏特徵遵循這六大原則,配合現代 GC(G1、ZGC、Shenandoah),即能構建高吞吐、低延遲、穩定無洩漏的 Java 系統。