【JVM】垃圾回收的四大演算法

xd會飛的貓發表於2020-05-31

GC垃圾回收

JVM大部分時候回收的都是新生代(伊甸區+倖存0區+倖存1區)。按照回收的區域可以分成兩種型別:Minor GC和Full GC(MajorGC)。

  • Minor GC:只針對新生代區域的GC,大多數Java物件的存活率都不高,Minor GC非常頻繁,回收速度快。
  • Full GC:發生在老年代的GC,經常會伴隨至少一次的Minor GC(但不一定會),Full GC掃描的範圍更廣泛,Full GC的速度比Minor GC慢10倍以上。

 

 

GC四大演算法

引用計數法

對於單個物件來說,當有引用發生,引用計數器就+1;當丟失引用,引用計數器就-1。當引用數減到0的時候,說明物件不再有用,被垃圾回收。引用計數法缺點是每次對物件賦值都要維護引用計數器,且計數器本身也有一定的消耗,難以處理引用迴圈(例如:物件雙方互相引用,但實際上二者為空,此時雙方引用都不為空)。JVM的實現一般不採用這種方式。

複製演算法

年輕代中使用的是Minor GC,這種Minor GC採用的是複製演算法。複製的思想是將記憶體分為2快,每次只用其中一塊,當這一塊記憶體用完,就將或者的物件複製到另一塊上面,複製演算法不會產生記憶體碎片

HotSpot JVM中年輕代可以分成三個部分:Eden區、Survivor0區,Survivor1區,預設比例為8:1:1。Survivor的兩個區在邏輯上可以視為from區和to區,每次GC後會交換from區和to區,在Eden區和from區滿之前,to區始終是為空的區。如果to區也被填滿了,所有物件移動到老年代。

新建立的物件一般會被分配到伊甸區,經過一次Minor GC後,如果物件還存活,就會被移到Survivor區。from區的物件如果繼續存活,且能夠被另一塊倖存區to區容納,則使用複製演算法將這些仍然存活的的物件複製到另一塊倖存區to區中,然後清理使用過的Eden和from區(下一次分配就從to區開始,to區成為下一次GC的from區),且這些物件的年齡設定為1,以後物件在倖存區每經歷一次Minor GC,物件的年齡就會+1,當物件的年齡到達某個閾值的時候,這些物件就會進入老年代。(閾值預設是15,可以通過-XX:MaxTenuringThreshhold來設定物件在新生代在存活的次數)。

這種演算法的優點了不會產生記憶體碎片,缺點是浪費記憶體空間,在HotSpot虛擬機器中8:1:1的比例下,可用記憶體為80%+10%,有10%的記憶體會被浪費掉。如果物件存活率很高,就需要將所有物件都複製一邊,並重置引用地址。

標記清除(Mark-Sweep)

老年代一般是由標記清除 或者 標記清除和標記整理的混合實現的。

標記清除演算法分為兩個步驟,先標記出要回收的物件,然後統一回收這些物件。

優點是節約記憶體空間,不需要額外空間。缺點是兩次掃描,標記和清除的效率都不高,耗時嚴重。標記清除後會產生大量不連續的記憶體碎片。記憶體碎片會導致以後程式需要分配大物件的時候,找不到足夠的連續記憶體,導致提前觸發GC。

 標記整理(Mark-Compact)

和標記清除一樣,先標記出要回收的物件,然後讓存活物件都向一端移動,直接清理掉端邊界 以外的記憶體。

優點是沒有記憶體碎片,缺點是效率不高,需要標記存活物件還要整理存活物件的引用地址,從效率上來說是不如複製演算法的。

還有一種折衷的方案,將標記清除和標記整理演算法相結合,一般直接標記清除,當GC達到一定次數的時候,進行一次標記整理,從而減少了移動物件的成本,又有處理記憶體碎片的步驟。

總結

效率排名:複製演算法>標記清除>標記整理

記憶體整齊度:複製演算法=標記整理>標記清理

記憶體利用率:標記整理=標記清理>複製演算法

四種演算法各有優劣,一般的JVM實現會採用分代收集演算法,根據不同代所具有的不同特點使用不同的演算法。

年輕代的特點是區域較小,物件存活率低,適合使用複製演算法。複製演算法的效率只和當前存活物件的大小有關,適用於年輕代的回收,記憶體利用率不高的問題HotSopt通過兩個survivor的設計進行和緩解,新生代可用容量為80%+10%,只有10%的記憶體被浪費掉。

老年代的特點是區域較大,物件存活率高,適合使用標記清除/標記整理演算法。

相關文章