一、判斷垃圾的算法
判斷對象是否為垃圾的核心是確定對象是否不再被使用。Java主要采用以下兩種算法:
1.?引用計數法(Reference Counting)
-
原理:每個對象維護一個引用計數器,記錄被引用的次數。當引用被添加時計數器加1,引用失效時減1。當計數器為0時,對象被視為垃圾。
-
缺點:
-
無法解決循環引用問題(例如:對象A和B互相引用,但無外部引用)。
-
-
Java未采用此算法,因為循環引用會導致內存泄漏。
2.?可達性分析算法(Reachability Analysis)
-
原理:從GC Roots(一組根對象)出發,遍歷所有可達對象。未被遍歷到的對象視為不可達,標記為垃圾。
-
GC Roots包括:
-
虛擬機棧(棧幀中的局部變量)中引用的對象。
-
方法區中靜態變量(
static
)引用的對象。 -
本地方法棧(JNI)中引用的對象(Native方法)。
-
Java虛擬機內部對象(如基本類型的Class對象)。
-
-
優點:解決了循環引用問題。
-
注意:即使對象不可達,也可能在
finalize()
方法中“復活”,但此方法不推薦使用。
3.?引用類型的影響
-
強引用(Strong Reference):普通引用(如
Object obj = new Object()
),只要存在,對象不會被回收。 -
軟引用(Soft Reference):內存不足時被回收,適合緩存。
-
弱引用(Weak Reference):下一次GC時被回收,適合臨時緩存。
-
虛引用(Phantom Reference):無法通過虛引用獲取對象,僅用于跟蹤回收狀態。
二、垃圾回收算法
Java通過不同算法實現垃圾回收,核心算法如下:
1.?標記-清除(Mark-Sweep)
-
步驟:
-
標記:遍歷所有對象,標記存活對象。
-
清除:回收未標記的對象。
-
-
缺點:
-
內存碎片化,影響大對象分配。
-
效率不穩定(對象越多,標記和清除越耗時)。
-
2.?復制算法(Copying)
-
步驟:將內存分為兩塊(如
Eden
和Survivor
區),每次使用一塊。存活對象復制到另一塊,清空原塊。 -
優點:無內存碎片,適合存活率低的新生代。
-
缺點:內存利用率僅50%(需預留空間)。
3.?標記-整理(Mark-Compact)
-
步驟:
-
標記:同標記-清除。
-
整理:將存活對象向內存一端移動,清除邊界外的空間。
-
-
優點:避免碎片化,適合老年代。
-
缺點:移動對象成本較高。
4.?分代收集算法(Generational Collection)
-
核心思想:根據對象存活周期將堆劃分為新生代(Young Generation)和老年代(Old Generation)。
-
新生代(存活率低):使用復制算法(如
Eden
和Survivor
區)。 -
老年代(存活率高):使用標記-清除或標記-整理算法。
-
-
觸發條件:
-
Minor GC:清理新生代。
-
Major GC/Full GC:清理老年代,通常伴隨STW(Stop-The-World)暫停。
-
三、垃圾收集器
不同垃圾收集器實現了上述算法,常見的有:
-
Serial:單線程,適合客戶端應用。
-
Parallel:多線程,注重吞吐量。
-
CMS(Concurrent Mark-Sweep):并發標記清除,減少停頓時間。
-
G1(Garbage-First):分區域收集,兼顧吞吐量和低延遲。
-
ZGC/Shenandoah:超低延遲(暫停時間<10ms),適用于大內存場景。
總結
-
判斷垃圾:Java通過可達性分析(GC Roots)識別不可達對象。
-
回收算法:根據對象生命周期使用分代收集,結合標記-清除、復制和標記-整理算法。
-
優化方向:權衡內存碎片、吞吐量和延遲,選擇適合的垃圾收集器(如G1/ZGC)。