1. 程式人生 > >JVM的四種GC演算法

JVM的四種GC演算法

介紹

程式在執行過程中,會產生大量的記憶體垃圾(一些沒有引用指向的記憶體物件都屬於記憶體垃圾,因為這些物件已經無法訪問,程式用不了它們了,對程式而言它們已經死亡),為了確保程式執行時的效能,java虛擬機器在程式執行的過程中不斷地進行自動的垃圾回收(GC)。關於 JVMGC 演算法主要有下面四種:

引用計數演算法(Reference counting)

演算法思想:

每個物件在建立的時候,就給這個物件繫結一個計數器。每當有一個引用指向該物件時,計數器加一;每當有一個指向它的引用被刪除時,計數器減一。這樣,當沒有引用指向該物件時,該物件死亡,計數器為0,這時就應該對這個物件進行垃圾回收操作。

核心思想:

為每個物件額外儲存一個計數器 RC ,根據 RC 的值來判斷物件是否死亡,從而判斷是否執行 GC 操作。

優點:

  • 簡單
  • 計算代價分散
  • “幽靈時間”短(幽靈時間指物件死亡到回收的這段時間,處於幽靈狀態)

缺點:

  • 不全面(容易漏掉迴圈引用的物件)
  • 併發支援較弱
  • 佔用額外記憶體空間

例子如圖:

初始狀態:
初始狀態
改變引用後:
這裡寫圖片描述

標記–清除演算法(Mark-Sweep)

演算法思想:

為每個物件儲存一個標記位,記錄物件的狀態(活著或是死亡)。分為兩個階段,一個是標記階段,這個階段內,為每個物件更新標記位,檢查物件是否死亡;第二個階段是清除階段,該階段對死亡的物件進行清除,執行 GC

操作。

優點

  • 最大的優點是,相比於引用計數法,標記—清除演算法中每個活著的物件的引用只需要找到一個即可,找到一個就可以判斷它為活的。

  • 此外,這個演算法相比於引用計數法更全面,在指標操作上也沒有太多的花銷。更重要的是,這個演算法並不移動物件的位置(後面倆演算法涉及到移動位置的問題)。

缺點

  • 很長的幽靈時間,判斷物件已經死亡,消耗了很多時間,這樣從物件死亡到物件被回收之間的時間過長。

  • 每個活著的物件都要在標記階段遍歷一遍;所有物件都要在清除階段掃描一遍,因此演算法複雜度較高。

  • 沒有移動物件,導致可能出現很多碎片空間無法利用的情況。

例子如圖

這個圖中,圓圈內灰色的物件就是已經死亡的物件,被標記為死亡,等待清除。
這裡寫圖片描述

標記–整理演算法

演算法思想

標記-整理法是標記-清除法的一個改進版。同樣,在標記階段,該演算法也將所有物件標記為存活和死亡兩種狀態;不同的是,在第二個階段,該演算法並沒有直接對死亡的物件進行清理,而是將所有存活的物件整理一下,放到另一處空間,然後把剩下的所有物件全部清除。這樣就達到了標記-整理的目的。

優點

  • 該演算法不會像標記-清除演算法那樣產生大量的碎片空間。

缺點

  • 如果存活的物件過多,整理階段將會執行較多複製操作,導致演算法效率降低。

例子

如圖:
這裡寫圖片描述

上面是標記階段,下面是整理之後的狀態。可以看到,該演算法不會產生大量碎片記憶體空間。

複製演算法

演算法思想

該演算法將記憶體平均分成兩部分,然後每次只使用其中的一部分,當這部分記憶體滿的時候,將記憶體中所有存活的物件複製到另一個記憶體中,然後將之前的記憶體清空,只使用這部分記憶體,迴圈下去。

注意:
這個演算法與標記-整理演算法的區別在於,該演算法不是在同一個區域複製,而是將所有存活的物件複製到另一個區域內。

優點

  • 實現簡單
  • 不產生記憶體碎片

缺點

  • 每次執行,總有一半記憶體是空的,導致可使用的記憶體空間只有原來的一半。

總結

不同演算法有不同的優點和缺點,除了引用計數法不常用外,其他三種演算法在現在的java虛擬機器上也是很常見的,間接說明了這幾個經典演算法還是有其適用性的。

理解 JVMGC 演算法能夠幫助我們更好地理解java的垃圾回收機制,例如,在 JVM 的年輕代使用的是複製演算法來進行垃圾回收(由於其中的存活物件比例較小);而在老年代,使用的卻是標記-清除法或標記-整理法(由於每次回收都只回收少量物件)