1. 標記-清除算法
最基礎的收集算法:“標記-清除”(Mark-Sweep)算法,算法分爲兩個階段:“標記”和“清除”。
- 算法過程
- 標記:首先標記處所有需要回收的對象(通過引用計數法和可達性分析判定是否可回收);
- 清除:完成標記後,統一回收所有被標記的對象。
- 缺點
- 效率問題,標記和清除兩個過程的效率都不高;
- 空間問題,標記清除後會產生大量不連續的內存碎片,空間碎片太多可能會導致以後在程序運行過程中需要分配較大對象時,無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。
2. 複製算法
爲了解決效率問題,“複製”(Copying)的收集算法將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。
當一塊內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用過的內存一次清理掉。
這樣每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜情況,只要一定堆頂指針,按順序分配內存即可,實現簡單,運行高效。代價是將內存縮小爲原來的一半。
- 現有階段的劃分優化
新生代的對象98%是“朝生暮死”的,所以並不需要按照1:1的比例劃分內存空間,而將內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。
當回收時,將Eden和Survivor中還存活的對象一次性複製到另外一塊Survivor空間上。最後清理掉Eden和剛纔使用過的Survivor空間。
HotSpot虛擬機默認Eden和Survivor的大小比例爲8:1,只有10%的內存會被浪費,雖然無法保證每次回首都只有不多於10%的對象存活,當Survivor空間不夠用時,需要依賴其他內存(Old,老年代)進行分配擔保(Handle Promotion),直接進入老年代中。
3. 標記-整理算法
複製算法在對象存活率較高時就要進行較多的複製操作,效率將會變低。更爲關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有兌現都100%存活的極端情況,所以老年代一般不能直接選用這種算法。
“標記-整理”(Mark-Compact)算法,標記過程仍然與“標記-清除”算法一樣,但後續步驟不是直接對可回收對象進行清理回收,而是讓所有存活的對象都向一端移動,然後清理掉端邊界意外的內存。從而不會產生“標記-清除”算法產生的空間碎片。
4. 分代收集算法
當前商業虛擬機的垃圾收集器都採用“分代收集”(Generational Collection)算法。
根據對象存活週期的不同將內存劃分爲幾塊,一般把Java堆分爲新生代和老年代,根據各個年代的特點採用最適合的收集算法。
-
新生代
根據其“朝生暮死”的特點,每次垃圾回收後只有少量存活,選擇複製算法,只需要付出少量存活對象的複製成本就可以完成收集 -
老年代
老年代中對象存活率高,沒有額外空間對它進行分配擔保,就必須使用“標記-清除”或者“標記-整理”算法。