1. 程式人生 > >【Java虛擬機器】垃圾收集器和記憶體分配策略

【Java虛擬機器】垃圾收集器和記憶體分配策略

垃圾收集器和記憶體分配策略

垃圾收集器

HotSpot虛擬機器所包含的所有收集器如下圖所示:
HotSpot虛擬機器的垃圾收集器
如上圖所示,如果兩個收集器之間存在連線,就說明它們可以搭配使用。虛擬機器所處的區域,則表示它是屬於新生代收集器還是老年代收集器。

Serial 收集器

單執行緒的新生代收集器
在進行回收垃圾時,必須暫停所有的工作執行緒,知道回收結束,也就是需要“Stop The World”。

Serial/Serial Old收集器的執行過程如下圖所示:
Serial/Serial Old收集器的執行過程

ParNew 收集器

ParNew收集器就是Serial收集器的並行多執行緒版本

ParNew/Serial Old收集器的執行過程如下圖所示:
ParNew/Serial Old收集器的執行過程

Parallel Scavenge 收集器

  • 新生代收集器,採用複製演算法,並行的多執行緒收集器
  • 達到一個可控制的吞吐量,即吞吐量 = 執行使用者程式碼時間/(執行使用者程式碼時間 + 垃圾收集時間)
  • 提供了兩個引數:最大垃圾回收停頓時間和吞吐量的大小
  • 可以自動調節引數(新生代的大小、Eden和Survivor的比例等)以提供最合適的停頓時間和最大的吞吐量

Parallel Scavenge/Parallel Old收集器的執行過程如下圖所示:
Parallel Scavenge/Parallel Old收集器的執行過程

Serial Old 收集器

Serial Old是Serial收集器的老年代版本,同樣是一個單執行緒收集器,使用“標記 - 整理”演算法

Serial/Serial Old收集器的執行過程如下圖所示:
Serial/Serial Old收集器的執行過程

Parallel Old 收集器

Parallel Old 是 Parallel Scavenge收集器的老年代版本,使用多執行緒和“標記 - 整理”演算法

Parallel Scavenge/Parallel Old收集器的執行過程如下圖所示:
Parallel Scavenge/Parallel Old收集器的執行過程

CMS 收集器(Concurrent Mark Sweep)

基於“標記 - 清除”演算法實現的
運作過程包括四個步驟:初始標記、併發標記、重新標記、併發清除

下圖是CMS收集器的執行過程:
CMS收集器的執行過程

其中初始標記和重新標記仍然需要“Stop The World”。初始標記僅僅只是標記一下GC Roots能直接關聯到的物件,併發標記就是進行GC Roots Tracing的過程,重新標記是為了修正併發標記期間使用者程式繼續運作而導致標記產生變動的那一部分物件的標記記錄。

CMS收集器的三個缺點:

  1. 佔用使用者執行緒的CPU資源
  2. 垃圾回收階段使用者執行緒需要執行,就需要預留足夠的記憶體空間給使用者執行緒使用。CMS執行期間預留的記憶體無法滿足程式需要時,就會出現“Concurrent Mode Failure”失敗,就會啟用Serial Old收集器來重新進行老年代的垃圾收集
  3. 基於“標記 - 清除”演算法實現,會產生大量碎片。為了解決這個問題,可以在進行FullGC之前進行記憶體碎片合併整理。

G1 收集器(Garbage-First)

  • 不需要其他收集器配合就能獨立管理真個GC堆
  • 整體是基於“標記 - 整理”演算法,區域性(兩個Region之間)上來是基於“標記 - 複製”演算法,也就是說不會產生空間碎片
  • 將整個Java堆分為多個大小相等的對立區域(Region),保留新生代和老年代的概念,每次回收價值最大的Region(價值大小為回收所獲得的空間大小和回收所需時間的經驗值)
  • 運作過程包括四個步驟:初始標記、併發標記、最終標記、篩選回收

下圖是G1收集器的執行過程:

G1收集器的執行過程
初始階段僅僅只是標記一下GC Roots能直接關聯到的物件,並且修改TAMS(Next Top at Mark Start)的值,讓下一階段使用者程式併發執行時,能在正確可用的Region中建立新物件。
篩選回收階段首先對各個Region的回收價值和成本進行排序,根據使用者所期望的GC停頓時間來指定回收計劃。

記憶體分配策略

新生代GC(Minor GC):指發生在新生代的GC。
老年代GC(Major GC / Full GC):指發生在老年代的GC。

物件優先在Eden分配

物件在新生代Eden區中分配,當Eden沒有足夠空間進行分配時,虛擬機器將發起一次Minor GC。

大物件直接進入老年代

虛擬機器提供了一個-XX:PretenureSizeThreshold引數,令大於這個設定值的物件直接在老年代分配。

長期存活的物件將進入老年代

虛擬機器為每個物件定義一個物件年齡計數器,如果物件在Eden出生冰經過一次Minor GC後仍然存活,並且能被Survivor容納的話,將被移動到Survivor空間中,並且物件年齡設為1。
物件在Survivor區中每“熬過”一次Minor GC,年齡就增加1歲,當它的年齡增加到一定程度,就將會被晉升到老年代中。物件晉升老年代的年齡閾值,可以通過引數-XX:MaxTenuringThreshold設定。

動態物件年齡判定

如果在Survivor空間中相同年齡所有物件大小的總和大於Survivor空間的一半,年齡大於或等於該年齡 物件就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。

空間分配擔保

在發生Minor GC之前,虛擬機器會先檢查老年代最大可用的了連續空間是否大於新生代所有物件總空間,如果這個條件成立,那麼Minor GC可以確保是安全的。
如果不成立,那麼檢查老年代最大可用的連續空間是否大於歷次晉升到老年代物件的平均大小,如果大於就進行Minor GC,否則將進行Full GC。

參考

  1. 深入理解Java虛擬機器[書籍]