文章目錄
- 1. 標記-清除算法
- 2. 復制算法
- 3. 標記-整理算法
- 總結與面試要點
在通過 可達性分析等算法識別出所有存活對象和垃圾對象后,垃圾收集器(GC:Garbage Collector)就需要執行回收操作來釋放垃圾對象所占用的內存。以下是三種最基礎、最經典的 垃圾回收算法
1. 標記-清除算法
標記-清除算法是最基礎的收集算法,正如其名,它分為“標記”和“清除”兩個階段
-
核心思想:
- 標記階段:首先,從GC Roots開始,遍歷所有可達對象,并為這些存活的對象打上一個“標記”
- 清除階段:然后,再次遍歷整個堆內存。在此過程中,將所有未被標記的對象(即垃圾對象)所占用的內存直接回收
-
過程:
- 初始狀態:內存中對象雜亂無章地排列
- 標記后:所有從GC Roots可達的存活對象被標記
- 清除后:所有未被標記的垃圾對象被回收,其內存變為空閑
-
優點:
- 實現簡單:算法思想直觀,易于實現
- 空間利用率高:不需要額外的空間來進行回收操作,它是在原始堆上直接進行的
-
缺點(非常重要):
- 執行效率不穩定:如果堆中包含大量對象,并且其中大部分是需要回收的,那么標記和清除兩個過程的執行效率都會隨著對象數量的增長而降低
- 內存碎片化:這是該算法最致命的缺點。清除之后,會產生大量不連續的內存碎片。雖然空閑內存的總量可能很大,但由于它們不連續,當程序需要分配一個較大的對象時,可能找不到一塊足夠大的連續內存空間,從而不得不提前觸發下一次垃圾收集動作,甚至導致
OutOfMemoryError
2. 復制算法
為了解決標記-清除算法帶來的內存碎片化問題,復制算法應運而生
-
核心思想:
它將可用的內存按容量劃分為大小相等的兩塊,比如稱為“From空間”和“To空間”。每次只使用其中的一塊(From空間)。當這一塊的內存用完了,就觸發GC。GC會將From空間中所有仍然存活的對象,按順序復制到另一塊未使用的To空間中,然后一次性地清理掉整個From空間。之后,From空間和To空間的角色互換,下次GC時再從新的From空間復制到新的To空間 -
過程:
- GC前:對象在From空間(Eden)分配,To空間(Survivor)是空的
- GC時:將From空間中的存活對象復制到To空間
- GC后:From空間被完全清空,所有存活對象都緊湊地排列在To空間的起始位置。然后From和To角色互換
-
優點:
- 無內存碎片:每次回收都是對整個半區進行內存整理,存活對象被復制到新空間后是連續存放的,不會產生碎片
- 實現簡單,運行高效:在對象存活率較低的情況下,效率非常高。因為只需要復制少量存活對象,而不需要處理大量垃圾對象。分配內存時也極其簡單,只需移動“堆頂指針”,按順序分配即可
-
缺點:
- 空間代價高昂:算法的代價是將可用內存縮小為了原來的一半,空間浪費嚴重
- 對存活率敏感:如果對象存活率很高,復制操作的開銷就會變得非常大,效率會急劇下降
-
實際應用(重要):
現代虛擬機(如HotSpot)的 新生代 普遍采用復制算法。因為研究表明,新生代中的對象98%以上是“朝生夕死”的,存活率極低。因此,在新生代使用復制算法,只需要付出少量存活對象的復制成本就可以完成收集細節優化:HotSpot虛擬機中的新生代并非簡單的1:1劃分。而是將內存分為一塊較大的Eden空間和兩塊較小的Survivor空間(分別稱為From和To,或S0和S1)。默認比例是Eden:S0:S1 = 8:1:1
每次分配只使用Eden和其中一塊Survivor。GC時,將Eden和正在使用的Survivor中的存活對象一次性復制到另一塊空閑的Survivor空間上
這樣,平時可用的內存空間是整個新生代容量的90%(80% Eden + 10% Survivor),空間浪費問題得到了極大的緩解
3. 標記-整理算法
標記-整理算法結合了“標記-清除”和“復制”算法的優點,旨在解決前兩者的弊端。它特別適用于對象存活率高的場景(如老年代)
-
核心思想:
它的標記過程與“標記-清除”算法完全一樣,但后續步驟不是直接對可回收對象進行清理,而是多了一個“整理”步驟- 標記階段:與標記-清除算法一致,從GC Roots開始標記所有存活對象
- 整理階段:將所有被標記的存活對象,向內存空間的一端移動,并緊湊地排列。然后,直接清理掉邊界以外的內存區域
-
過程:
- 初始狀態:內存中對象雜亂排列
- 標記后:存活對象被標記
- 整理后:所有存活對象被移動到內存的一端,形成連續的已用空間,另一端則成為連續的空閑空間
-
優點:
- 無內存碎片:解決了標記-清除算法的碎片化問題,使得內存分配可以簡單地使用“指針碰撞”技術
- 無空間浪費:不像復制算法那樣需要犧牲一半的內存空間
-
缺點:
- 執行效率較低:標記和清除的效率問題依然存在。更重要的是,“整理”階段需要移動大量對象,并且在移動后,還需要更新所有引用這些對象的指針,這是一個非常耗時的操作,需要暫停用戶應用程序(STW: Stop The World)
-
實際應用:
由于老年代(Old Generation)中的對象存活率高,且GC頻率遠低于新生代,因此不適合使用復制算法(復制開銷大),而標記-清除算法的碎片問題又難以接受。所以,標記-整理算法通常用于老年代的垃圾收集
總結與面試要點
算法 | 優點 | 缺點 | 適用場景 |
---|---|---|---|
標記-清除 | 實現簡單,無空間開銷 | 效率不高,內存碎片化(致命) | 作為其他算法的基礎,或在特定GC器中與其它算法結合使用 |
復制 | 無內存碎片,效率高(存活率低時) | 空間浪費嚴重(可用內存減半),存活率高時效率低 | 新生代 |
標記-整理 | 無內存碎片,無空間浪費 | 效率低,移動和更新引用開銷大 | 老年代 |
面試回答策略:
- 清晰闡述三種算法的核心思想和執行步驟。畫圖輔助說明是很好的方式
- 精準說出每種算法的優缺點,特別是它們的“致命缺點”,這是面試官考察你是否理解深刻的關鍵
- 將理論與實踐結合:一定要說明這些基礎算法在現代JVM分代收集(Generational Collection)中的具體應用。即“分代假說”:
- 新生代:對象存活率低 -> 采用復制算法
- 老年代:對象存活率高 -> 采用標記-清除或標記-整理算法(或它們的混合形式)