1. 程式人生 > >(面試題)有關JVM垃圾回收機制的那些演算法

(面試題)有關JVM垃圾回收機制的那些演算法

三種垃圾回收演算法

  • 標記-清除(年老代)
  • 標記-整理(即標記-壓縮)(年老代)
  • 複製(年輕代)

1、標記-清除演算法

 

原理

  • 從根集合節點進行掃描,標記出所有的存活物件,最後掃描整個記憶體空間並清除沒有標記的物件(即死亡物件)

適用場合

  • 存活物件較多的情況下比較高效
  • 適用於年老代(即舊生代)

缺點

  • 容易產生記憶體碎片,再來一個比較大的物件時(典型情況:該物件的大小大於空閒表中的每一塊兒大小但是小於其中兩塊兒的和),會提前觸發垃圾回收
  • 掃描了整個空間兩次(第一次:標記存活物件;第二次:清除沒有標記的物件)

注意:

  • 在該情況下,記憶體不規整,物件的記憶體分配採用"空閒列表法",

 

2、標記整理演算法

原理:

  • 從根集合節點進行掃描,標記出所有的存活物件,最後掃描整個記憶體空間並清除沒有標記的物件(即死亡物件)(可以發現前邊這些就是標記-清除演算法的原理),清除完之後,將所有的存活物件左移到一起。

適用場合:

  • 用於年老代(即舊生代)

缺點:

  • 需要移動物件,若物件非常多而且標記回收後的記憶體非常不完整,可能移動這個動作也會耗費一定時間
  • 掃描了整個空間兩次(第一次:標記存活物件;第二次:清除沒有標記的物件)

優點:

  • 不會產生記憶體碎片

注意:

  • 在該情況下,記憶體規整,物件的記憶體分配採用"指標碰撞法"。

 

3、複製演算法

原理:

  • 從根集合節點進行掃描,標記出所有的存活物件,並將這些存活的物件複製到一塊兒新的記憶體(圖中下邊的那一塊兒記憶體)上去,之後將原來的那一塊兒記憶體(圖中上邊的那一塊兒記憶體)全部回收掉

適用場合:

  • 存活物件較少的情況下比較高效
  • 掃描了整個空間一次(標記存活物件並複製移動)
  • 適用於年輕代(即新生代):基本上98%的物件是"朝生夕死"的,存活下來的會很少

缺點:

  • 需要一塊兒空的記憶體空間
  • 需要複製移動物件

注意:

  • 在該情況下,記憶體規整,物件的記憶體分配採用"指標碰撞法"。
  • 以空間換時間:通過一塊兒空記憶體的使用,減少了一次掃描。

 

分代收集演算法

分代收集演算法理論來源於統計學。IBM公司的專門研究發現,物件的生存週期總體可分為三種:新生代、老年代和永久代。因此可以根據各個年代的特點採用適當的垃圾回收演算法。比如新生代的物件在每次垃圾時都會有大量的物件死去,只有很少一部分存活,那就可以選擇標記-複製演算法。另外,在新生代中每次死亡物件約佔98%,那麼在標記-複製演算法中就不需要按照1:1的比例來劃分記憶體區域,而是將新生代細分為了一塊較大的Eden和兩塊較小的Survivor區域,HotSpot中預設這兩塊區域的大小比例為8:2。每次新生代可用區域為Eden加上其中一塊Survivor區域,共90%的記憶體空間,這樣就只有10%的記憶體空間處在被閒置狀態。在進行垃圾回收時,存活的物件被轉移到原本處在“空閒的”Eden區域。如果某次垃圾回收後,存活物件所佔空間遠大於這10%的記憶體空間時,也就是Survivor空間不夠用時,需要額外的空間來擔保,通常是將這些物件轉移到老年代。對於老年代來說,大部分物件都處在存活狀態。同時,如果一個大物件要在該區域進行分配,而記憶體空間又不足,那麼在沒有外部記憶體空間擔保的情況下,就必須選用標記-清除或者標記-整理演算法來進行垃圾回收了。

總而言之,分代收集只是根據物件生存週期的不同來選擇不同的演算法,其本身並沒有任何新思想。


垃圾回收機制

年輕代分為Eden區和survivor區(兩塊兒:from和to),且Eden:from:to==8:1:1

1)新產生的物件優先分配在Eden區(除非配置了-XX:PretenureSizeThreshold,大於該值的物件會直接進入年老代);

2)當Eden區滿了或放不下了,這時候其中存活的物件會複製到from區(這裡,需要注意的是,如果存活下來的物件from區都放不下,則這些存活下來的物件全部進入年老代),之後Eden區的記憶體全部回收掉;注意:如果是Eden區沒有滿,但是來了一個小物件Eden區放不下,這時候Eden區存活物件複製到from區後,清空Eden區,之後剛才的小物件再進入Eden區

3)之後產生的物件繼續分配在Eden區,當Eden區又滿了或放不下了,這時候將會把Eden區和from區存活下來的物件複製到to區(同理,如果存活下來的物件to區都放不下,則這些存活下來的物件全部進入年老代),之後回收掉Eden區和from區的所有記憶體;

4)如上這樣,會有很多物件會被複制很多次(每複製一次,物件的年齡就+1),預設情況下,當物件被複制了15次(這個次數可以通過:-XX:MaxTenuringThreshold來配置),就會進入年老代了

5)當年老代滿了或者存放不下將要進入年老代的存活物件的時候,就會發生一次Full GC(這個是我們最需要減少的,因為耗時很嚴重)


總結:

  • 年輕代:複製演算法
  • 年老代:標記-清除或標記-整理(前者相較於後者會快一些但是會產生記憶體碎片,後者相較於前者不會產生記憶體碎片但是由於要移動存活物件所以會慢一些)
  • 以上這種年輕代與年老代分別採用不同回收演算法的方式稱為"分代收集演算法",這也是當下企業使用的一種方式
  • 每一種演算法都會有很多不同的垃圾回收器去實現,在實際使用中,根據自己的業務特點做出選擇就好