JVM垃圾回收器、記憶體分配與回收策略

大資料學習與分享發表於2020-11-26

新生代垃圾收集器

1. Serial收集器

serial收集器即序列收集器,是一個單執行緒收集器。

序列收集器在進行垃圾回收時只使用一個CPU或一條收集執行緒去完成垃圾回收工作,並且會暫停其他的工作執行緒(stop the world),直至回收完成。適用於執行在client模式下的JVM。

在單CPU年代,序列收集器是預設的垃圾回收器,minor GC和major GC的過程都是用一個執行緒去處理的。

啟用方式:-XX: +UseSerialGC

2. ParNew收集器

parNew收集器即並行收集器,採用的是多執行緒方式進行垃圾回收,可以理解為Serial收集器的多執行緒版本,吞吐量要比序列高很多,是伺服器級別的虛擬機器預設使用的,用來處理新生代的垃圾回收器。

因為採用的是並行多執行緒方式,建議在多CPU環境下使用,否則和Serial沒有區別。

啟用方式:-XX: +UseParNewGC

3. Parallel Scavenge收集器

並行的多執行緒垃圾收集器,採用複製演算法進行垃圾回收,非常適合伺服器做計算任務時使用。

一般的垃圾回收器是在儘量短的時間內進行垃圾回收,這樣程式與使用者互動的時間間隔比較小,不會出現長時間的卡頓現象。但是Parallel Scavenge更側重於系統的吞吐量,高效的利用CPU,優先處理計算任務,適合互動少、運算多的場景。

通過引數-XX: MaxGcPauseMills設定GC最大停頓時間,通過引數-XX: GCTimeRatio設定吞吐量大小。

啟用方式:-XX: +UseParallelGC

老年代垃圾收集器

1. Serial Old收集器

serial收集器的老年代版本,同樣是單執行緒收集器、stop the world,使用標記整理演算法。一般啟用方式:UseSerialGC是Serial + Serial OldUseParNewGC是ParNew + Serial OldUseParallelGC是Parallel Scavenge + Serial Old

2. Parallel Old收集器

老年代版本的Parallel Scavenge,使用多執行緒 + 標記整理演算法。啟用方式:-XX: +UseParallelOldGC

3. CMS垃圾收集器

CMS收集器的主要目的是使垃圾回收造成的停頓時間最短,提高服務響應速度,使用標記清除演算法,具有併發收集(使用者執行緒與垃圾收集併發執行)、低停頓的特點。執行過程分為以下四個步驟:

1. 初始標記:stop the world,只是標記一下GC Roots能直接關聯到的物件,速度快

2. 併發標記:進行GC RootsTracing過程

3. 重新標記:stop the world,修正併發標記期間因使用者程式繼續執行而導致的標記產生變動的那部分物件的標記記錄。這個階段停頓時間相對初始標記時間長,比並發標記時間短

4. 併發清除注意:

  1. CMS收集器對CPU資源敏感,這是面向併發程式設計的共性
  2. 無法處理浮動垃圾(CMS垃圾收集階段,使用者執行緒仍在執行,因此會有新的垃圾生成,這部分垃圾只能在下一次GC時再清理,即浮動垃圾),可能出現"Concurrent Mode Failure"失敗導致另一次full GC

啟用方式:-XX:+UseConcMarkSweepGC

G1收集器

G1收集器是基於標記整理演算法實現的收集器,所以它不會產生記憶體空間碎片,並且可以精確的控制停頓時間。能讓使用者明確指定在一個長度為M毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒。

G1的設計原則就是簡單可行的效能調優,只需宣告以下引數即可:
-XX:+UseG1GC -Xmx16g -XX:MaxGCPauseMills=300

其中,-XX:+UseG1GC表明開啟G1收集器,-Xmx16g設定堆記憶體為16g,-XX:MaxGCPauseMills=300設定GC停頓最大時間為300ms。如果我們需要調優,在記憶體一定的情況下,可以考慮修改該引數,當然還要根據實際業務場景來處理。

G1取消了堆內結構的新生代、老年代的物理空間劃分,將整個Java堆劃分為大小固定的獨立區域,後臺維護一個優先列表來跟蹤這些區域的垃圾堆積程度,每次根據允許收集的時間,優先回收垃圾最多的區域。

G1中的Humongous區域用於儲存生命週期較短的巨型物件(一個物件所佔空間超過了分割槽容量的50%),如果一個Humongous區無法裝下一個巨型物件,G1會尋找連續的H分割槽來儲存,如果沒有連續的H區滿足這種情況,有時候會觸發full GC。

G1收集器的運作主要劃分為以下四個步驟:

  1. 初始標記:標記GC Roots能直接關聯到的物件,需要停頓執行緒,但耗時很短
  2. 併發標記:從GC Roots開始對堆中物件進行可達性分析,找出存活的物件,這階段耗時較長,但可與使用者程式併發執行
  3. 最終標記:修正在併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分標記記錄
  4. 篩選回收:對各個Region的回收價值和成本進行排序,根據使用者所期望的GC停頓時間來制定回收計劃

記憶體分配與回收策略

物件的記憶體分配主要是指在Java堆上的分配,通常會優先分配在新生代,然後經歷一系列GC後仍然存活的物件會進入到老年代。少數情況下,一些物件也會直接進入到老年代。

-Xms:設定初始化堆記憶體,-Xmx設定最大堆記憶體,設定為相等可防止記憶體抖動(剩餘記憶體大於70%、小於40%時,自動觸發記憶體擴大或縮小)

1. 物件會優先分配在新生代

新生代劃分為一個eden區和兩個survivor區(from survivor、to survivor)。初始階段,新建立的物件會分配給eden區。新生代發生的GC成為minor GC即Young GC,主要分為以下幾個過程:

  1. 隨著eden區儲存的物件不斷增多,當eden區剩餘空間無法儲存新生成的物件時會觸發GC
  2. 經過minor GC後仍然存活的物件會進入from survivor區
  3. 當再次觸發GC時,會掃描eden區和from survivor區,對這兩個區域進行垃圾回收,仍然存活的物件會被複制到to survivor區,同時這些存活的物件年齡加1
  4. 清空eden、from survivor區中的物件,並將from survivor和to survivor區互換
  5. 頻繁執行上述過程,當剩餘存活物件年齡達到15(預設)時,這些物件會進入老年代,通過引數-XX:MaxTenuringThreshold控制

-Xmn用來設定新生代大小,一般設定為整個堆記憶體的3/1或者1/4

-XX:NewRatio設定新生代與老年代的堆記憶體比例

-XX:SurvivorRatio設定eden區和survivor區之間的比例

2. 老年代儲存的物件

發生在老年代的GC是major GC,回收速度會比minor GC慢。

上文已經說明了物件進入老年代的一種情況即長期存活的物件會進入老年代,這裡再來看看其他情況:

1. 大物件直接進入老年代大物件需要的連續儲存空間(如陣列)大於新生代剩餘空間時,會直接進入老年代。

通過引數-XX:PretenureSizeThreshold設定,大於該引數值的物件會直接進入老年代(避免新生代中大量物件的拷貝,效率低)

注意:PretenureSizeThreshold引數只對部分垃圾回收器有效,比如Serial和ParNew

2. 如果survivor區相同年齡所有物件大小的總和大於survivor空間的一半,年齡大於或等於該年齡的物件可以直接進入老年代,無需等到MaxTenuringThreshold設定的年齡

注意:永久代不屬於堆空間,通過引數-XX:PermSize,-XX:MaxPermSize控制大小

3. Full GC

full GC是針對整個Java堆空間進行垃圾回收,包括新生代和老年代,會造成stop world。要儘量避免full GC,它會影響程式的穩定性。

導致Full GC的幾點原因:

1. 老年代空間不足從年輕代進入老年代的物件所佔空間大於老年代剩餘空間大小。eden區調大一些,儘量讓物件在新生代minor GC回收,而不是集中在老年代進行major GC,儘量不要建立特別大的物件

2. 垃圾回收演算法用的不對比如在老年代使用複製收集演算法

3. 永久代空間不足

4. 被HandlePromotionFailure引數強制Full GC

在發生minor GC時,虛擬機器會檢測之前每次晉升到老年代的平均大小是否大於老年代的剩餘空間大小,如果大於,則改為進行full GC。如果小於,則根據HandlePromotionFailure的設定是否允許擔保分配記憶體失敗:如果允許失敗,則只進行minor GC;反之,則進行full GC。

但是如果發生HandlePromotionFailure失敗,則會進行full GC。


 

關注微信公眾號:大資料學習與分享,獲取更對技術乾貨

相關文章