徹底理解JVM垃圾回收-垃圾收集算法(八)

分代收集理論

當前商業虛擬機的垃圾收集器大多數遵循了”分代收集“的理論進行設計的。遵循分代收集理論,Java的堆空間被劃分爲新生代(Yong Generation)和老年代(Old Generation)兩個區域。在新生代中,每次垃圾收集時都能發現有大批的對象死去,而每次回收後存活的少量對象,將會逐步晉升到老年代中存放。

標記-清除算法

該算法分爲兩個階段,”標記階段“和”清除字段“:首先標記出所有需要回收的對象,在標記完成之後,統一回收掉所有被標記過的對象,也可以反過來標記所有存活的對象,清理未標記的對象。標記過程就是對象是否屬於垃圾對象的判定過程。該算法主要存在兩個缺點:
(1)執行效率不穩定,如果Java堆中包含大量對象,而且其中大部分是需要回收的對象,這時必須進行大量標記和清楚的動作,導致標記和清除兩個過程的執行效率都隨着對象數量增長而降低
(2)內存空間的碎片化問題,標記、清楚之後會產生大量不連續的內存碎片,空間碎片太多可能會導致當以後在程序運行過程中需要分配較大對象時無法找到足夠的連續內存而不得提前觸發另一次垃圾收集動作。
標記清除算法的執行過程如圖:

標記清除算法

標記-複製算法

標記-複製算法簡稱複製算法。爲了解決標記-清除算法面對大量可回收對象時執行效率低的問題。它可將內存按照容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一框的內存用完了,就將還活着的對象複製到另一塊上面,然後再把已使用過的內存空間一次清理掉。如果內存中多數對象還是存活的,這種算法將會產生大量的內存空間複製的開銷,但對於多數對象都是可回收的情況,算法需要複製的就是佔少數的存活對象,而且每次都是針對整個半區進行內存回收,分配內存時也就不用考慮空間碎片的複雜情況,只要移動堆頂指針,按順序分配即可。這種實現簡單,運行高效,但是缺陷也是顯而易見的,這種複製回收算法的代價是將可用內存縮小爲原來的一半,造成空間浪費的。複製算法的執行過程如圖:
複製算法
現在商用的Java虛擬機大多數優先採用了這種收集算法去回收新生代。因爲新生代對象大多數情況下都是”朝生夕滅“(第一次Yang GC基本大多數對象都可被回收),所以新生代內存並不需要將內存按照1:1的比例進行劃分新生代的內存空間,而是將新生代內存分爲一塊較大的Eden空間和兩塊較小的Survivor空間,每次分配內存只使用Eden和其中一塊Survivor。發生垃圾收集時,將Eden和Survivor中仍然存活的對象複製到另一個Survivor區域,然後直接清理Eden和已經使用過的那塊Survivor空間。HotSpot虛擬機默認Eden和Survivor的大小比例爲8:1:1,即每次新生代中可用內存空間爲整個新生代容量的90%(Eden空間+一個Survivor區域),只有一個Survivor(10%)的新生代空間會被“浪費”。當然,對象經歷回收後,如果仍然有超過10%的內存空間的對象存活時,就需要進行內存分配的擔保措施,一般就需要依賴其他內存區域(大多是老年代)進行擔保分配。如果另一塊的Survivor區域沒有足夠的空間存放存活對象,那麼這些對象便通過分配擔保機制直接進入老年代。

標記-整理算法

標記-複製算法在對象存活率比較高時就要進行較多的複製操作,效率將會降低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。
針對老年代的對象的存亡特徵, 產生了標記-整理算法,其標記過程仍然與“標記-清除”算法一樣,但後續步驟不是直接清理可回收對象,而是讓所有存活的對象都向內存空間一端移動,然後直接清理掉邊界以外的內存,“標記-整理”算法的執行過程如圖:
標記整理
標記-清除算法與標記-整理算法的本質差異在於前者是一種非移動式的回收算法,而後者是移動式的。是否移動回收後的存活對象是一項優缺點並存的風險決策:
如果移動存活對象,尤其在老年代這種每次回收都有大量對象存活區域,移動存活對象並更新所有引用這些對象的地方將會是一種極爲負重​的操作,而且這種對象移動操作必須全過程暫停用戶應用程序才能進行(Stop the Wold->STW)。但是如果跟標記-清楚那樣完全不考慮移動和整理存活對象的話,彌散於堆中的存活對象將導致空間碎片化問題只能依賴更爲複雜的內存分配器和內存訪問器來解決。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章