1. 程式人生 > >深入理解JVM——虛擬機器GC

深入理解JVM——虛擬機器GC

物件是否存活

Java的GC基於可達性分析演算法(Python用引用計數法),通過可達性分析來判定物件是否存活。這個演算法的基本思想是通過一系列"GC Roots"的物件作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鏈,當一個物件到GC Roots沒有任何引用鏈相連時(圖論稱之為不可達),則證明此物件是不可用的。

無論引用計數法,還是可達性分析都離不開“引用”的概念。Java將引用分為四種(強引用、軟引用,弱引用,虛引用),這四種引用強度依次逐漸減弱。

  • strong reference強引用,垃圾收集器永遠不會回收存在強引用的物件。(如Object obj = new object()就是一個強引用)

  • soft reference軟引用,描述一些還有用但是並非必須的物件。系統將要發生記憶體溢位異常之前,將會把這些軟引用物件列入回收範圍中進行二次回收。如果這次回收之後還是不夠記憶體,才會丟擲記憶體溢位異常。

  • weak reference弱引用,也是用來描述非必須物件的,但是它的強度更弱。被弱引用的物件只能活到下一次GC發生之前。當GC發生,無論當前記憶體是否充足都會回收弱引用物件。

  • phantom reference虛引用(幽靈引用),是最弱的一種引用關係。為一個物件設定虛引用關聯的唯一目的就是能在這個物件被收集器回收時收到一個系統通知。

在可達性演算法中不可達的物件,不會直接死亡,如果它還沒有執行finalize方法,虛擬機器將在一個優先順序很低的執行緒中觸發它的finalize方法(不保證完成),如果此時物件又將自己關聯到引用鏈上,那麼GC將把它移出“即將回收”的集合。

由於finalize方法只會被執行一次,所以這個“自救”過程也只會經歷一次。

垃圾回收方法

1、標記清除法

首先標記所有需要回收的物件,然後統一回收所有要回收的物件。方法簡單,但是效率不高,並且產生了很多記憶體碎片。

2、複製演算法

將記憶體分為大小相等的兩塊,每次只使用一塊。當一塊的記憶體用完了,就將還存活的物件複製到另一塊記憶體上,然後把使用過的那一半記憶體空間一次性清理掉。這樣再分配記憶體時只需要移動堆頂指標,實現簡單,執行高效。但是代價是實際可用記憶體縮小了一半,實在太高。

因為新生代大部分物件都是”朝生夕死“,所以虛擬機器將記憶體分為1個Eden和2個Survivor空間,大小比例預設為8:1:1。當回收時,將Eden和Survivor中還存活著的物件複製到另一個Survivor空間上,然後清理掉Eden和剛才用過的Survivor空間。這樣子就只需要犧牲10%的新生代記憶體。

分配擔保:因為存在1個Survivor空間存放不了Eden和另一個Survivor空間的物件的情況,此時那些存活的物件由於無法放入該Survivor空間,將直接進入老年代。)

3、標記-整理演算法

老年代物件存活率高,複製演算法在這時候效率很低,而且老年代沒有額外空間做分配擔保,所以不適用。根據這種特點,有人提出了標記-整理演算法。

首先標記所有要回收的物件,然後所有存活的物件都向一端移動,然後直接清理邊界以外的記憶體。

4、分代收集演算法

前面三個垃圾回收演算法都算是鋪墊,現在商業虛擬機器的垃圾收集都採用”分代收集“。將Java堆分成新生代和老年代,新生代中每次GC有大量物件死去,只有少量存活,所以採用複製演算法;老年代中物件存活率高,沒有額外空間進行分配擔保,就採用標記-清除或者標記-整理演算法。

垃圾收集器

 

新生代:serial收集器

單執行緒,必須暫停其他所有工作執行緒(Stop The World),直到它收集結束。STW難以接受,但是簡單高效。

新生代:parNew收集器

serial的多執行緒版本。server模式下首選,因為它可以和cms配合使用。

新生代:parallel scavenge收集器

專注於達到可控制吞吐量的收集器,主要適合後臺運算而不用太多互動的任務。

老年代:serial old收集器

serial的老年代版本,單執行緒。使用標記-整理演算法。

 

老年代:parallel old收集器

parallel scavenge的老年代版本。使用標記-整理演算法。

 

老年代:cms收集器

經歷四個階段:

  • 初始標記:需要STW,僅僅標記一下GC Roots能直接關聯的物件,所以很快。

  • 併發標記:進行GC Roots Tracing

  • 重新標記:需要STW,修正併發標記期間因程式執行導致標記產生變動的那一部分物件的標記記錄,時間比初始標記長,但是遠比並發標記短

  • 併發清除:清除物件

由於耗時最長的併發標記和併發清除過程,收集器執行緒都可以和使用者執行緒一起工作,所以總體來說cms收集器的記憶體回收過程是和使用者執行緒一起併發執行的,停頓很短。

缺點:cpu資源敏感,特別是cpu核數較少時;無法處理浮動垃圾;cms基於標記-清除演算法,容易產生大量記憶體碎片。

G1收集器

最新的收集器,效能優秀,但是太複雜不再贅述。

記憶體分配和回收策略

  • 物件優先在Eden分配,如果啟動了TLAB,將按照執行緒優先在TLAB上分配。

  • 當Eden不夠空間時,虛擬機發起一次Minor GC(新生代GC)

  • 大物件直接進入老年代

  • 長期存活的物件將進入老年代(存活一次GC age+1,預設age 15時進入老年代)

  • Survivor空間中相同年齡所有年齡大小 的總和大於Survivor空間的一半 時,年齡大於等於該年齡的物件可以直接進入老年代

  • Minor GC時,虛擬機器會檢查老年代的剩餘空間是否大於新生代物件總大小or歷次晉升的平均大小,如果是那麼就會進行Minor GC,否則認為老年代無法提供分配擔保,進行Full GC。