從如何斷定對象消亡的角度出發,垃圾收集算法能夠劃分爲「引用計數式垃圾收集」(Reference Counting GC)和「追蹤式垃圾收集」(Tracing GC)兩大類,這兩類也常被稱做「直接垃圾收集」和「間接垃圾收集」。因爲束流Java虛擬機中使用 的都是「追蹤式垃圾收集」,因此後續介紹的垃圾收集算法都是屬於追蹤式的垃圾收集。算法
當前商業虛擬機的垃圾收集器,大多數都遵循了「分代收集」的理論進行設計。
主要簡歷在兩個分代假說之上:
一、弱分代假說:絕大多數對象都是「朝生夕滅」的。
二、強分代假說:熬過越多此垃圾收集過程的對象就越難以消亡。
這兩個分代假說奠基了多款經常使用的垃圾收集器的一致設計原則:收集器應該將Java堆劃分出不一樣的區域,而後將回收對象依據其年齡(對象熬過垃圾收集過程的次數)分配到不一樣的區域之中存儲。
把分代收集理論具體放到如今商用的Java虛擬機裏,設計者通常至少會把Java堆劃分爲新生代(Young Generation) 和 老年代(Old Generation兩個區域。在新生代中,每次垃圾收集時都有大批對象死去,而每次回收後存活的少許對象,將會逐步晉升到老年代中存放。優化
標記-清除算法,分爲「標記」和「清除」兩個階段:首先標記全部須要回收的對象,標記完成後,統一回收掉全部被標記的對象,也能夠反過來,標記存活的對象,統一回收全部未被標記的對象。
這個算法有兩個主要的缺點:
第一個是執行效率不穩定,若是Java堆中有大部分是須要回收的對象,這個會進行大量標記和清除動做,致使標記和清除兩個過程的執行效率隨着對象數量增加而下降。
第二個是內存碎片化問題,標記、清除以後會產生大量不連續的內存碎片,空間碎片太多會致使當須要大對象時找不到足夠的連續內存,而提早觸發另外一次垃圾收集動做。
由於這兩個缺點的緣由,纔會產生後續一些針對於修復這兩個缺點的算法。
標記清除算法示意圖:
設計
標記複製算法也被簡稱Wie複製算法,爲了解決標記清除算法面對大量可回收對象時執行效率低的問題,而產生的一種稱爲「半區複製」的垃圾收集算法。
原理是:將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊當這一塊內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉。
這種算法不用考慮空間碎片化,只須要移動堆指針,按順序分配便可,實現簡單,運行高效,但缺點也是顯而易見的,就是將可用內存縮小了原來的一半。
標記複製算法示意圖:
因爲新生代裏的對象「朝生夕滅」,針對這個特色,又產生了一種更優化的半區複製分代策略,稱爲「Appel式回收」。具體作法是把新生代分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次分配內存只是用Eden和其中一塊Survivor。當發生垃圾收集時,將Eden和Survivor中任然存活的對象一次性複製到另一塊Survivor空間上,而後直接清理掉Eden和Survivor空間。
HotSpot虛擬機默認Eden和Survivor的大小比例是8:1,也就是說每次可利用的空間爲新生代的90%,只有10%的空間會暫時「浪費」。
若是另一塊兒Survivor沒有足夠的空間存放存活的對象了,這些對象將經過分配擔保機制直接進入到老年代。指針
標記複製算法在對象存活率較高時就要進行較多的複製操做,效率將會下降。更關鍵的是,若是不浪費50%的空間,就須要有額外的空間進行分配擔保,以應對被使用的內存中全部對象都100%存活的極端狀況,因此在老年代通常不能直接選用這種算法。
針對老年代對象的存亡特徵,產生了另一種有針對性的「標記整理」算法。標記的過程和「標記-清除」算法同樣,也是判斷對象是否屬於垃圾的過程。但後續步驟是讓全部存活的對象都向內存空間一端移動,而後直接清理掉邊界之外的內存。
標記整理算法示意圖:
在這種算法中,在移動存活對象,尤爲是在老年代這種每次回收都有大量對象存活區域,移動存活對象並更新全部引用這些對象的地方將會是一種極爲負重的操做,並且這種移動操做必須在暫停用戶應用程序才能進行(也就是「Stop The World」)。可是不移動又會形成內存空間碎片化。因此各有利弊,從垃圾收集的停頓時間來看,不移動對象停頓時間更短,但從整個程序的吞吐量來看,移動對象會更划算。因此要依狀況而定。
還有一種「和稀泥」的解決方案,就是平時採用標記清除算法,直到內存空間碎片化程度已經大到影響對象分配時,再採用標記整理算法收集一次,以得到規整的內存空間。對象