第3章 第10節 Java進階 - JVM記憶體機制(中)

張僅發表於2020-09-27

大家好,上一小節中我們主要介紹了JVM記憶體機制的基礎知識點,包括記憶體的分配以及回收策略等。本小節我們主要介紹垃圾回收演算法以及垃圾收集器,並且對當前使用較廣的CMS垃圾收集器做了較為詳細的闡述。垃圾回收演算法是JVM相關技術考察中的高頻考點,希望大家可以有效理解與掌握,在面試中清晰闡述。

有效掌握JVM記憶體相關技術原理對於我們的日常開發工作也有很大的幫助。好了,讓我們一起來學習吧~
 

(1)JVM垃圾回收演算法有哪些?(重點掌握)

答:HotSpot 虛擬機器採用了root根搜尋方法來進行記憶體回收,常見的回收演算法有標記-清除演算法,複製演算法和標記整理演算法。
 

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

標記-清除演算法執行分兩階段。第一階段從引用根節點開始標記所有被引用的物件,第二階段遍歷整個堆,把未標記的物件清除。此演算法需要暫停整個應用,並且會產生記憶體碎片。

圖片說明

 

複製演算法:

複製演算法把記憶體空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的物件複製到另外一個區域中。複製演算法每次只處理正在使用中的物件,因此複製成本比較小,同時複製過去以後還能進行相應的記憶體整理,不會出現“碎片”問題。當然,此演算法的缺點也是很明顯的,就是需要兩倍記憶體空間。

圖片說明

 

標記-整理演算法:

標記-整理演算法結合了“標記-清除”和“複製”兩個演算法的優點。也是分兩階段,第一階段從根節點開始標記所有被引用物件,第二階段遍歷整個堆,清除未標記物件並且把存活物件“壓縮”到堆的其中一塊,按順序排放。此演算法避免了“標記-清除”的碎片問題,同時也避免了“複製”演算法的空間問題。

圖片說明

 

解析:

垃圾回收演算法是垃圾收集器的演算法實現基礎,年輕代垃圾回收一般採用複製演算法,老年代垃圾回收一般採用標記-清除和標記-整理演算法。希望大家對照著演算法示意圖,對演算法的原理與過程加以理解與掌握。接下來,我們一起來看垃圾回收演算法的具體實現,那就是垃圾收集器吧。

 

(2)JVM中的垃圾收集器有了解嗎?(重點掌握CMS收集器)

答: JVM中的垃圾收集器主要包括7種,即Serial,Serial Old,ParNew,Parallel Scavenge,Parallel Old以及CMS,G1收集器。如下圖所示:

圖片說明

Serial收集器:

Serial收集器是一個單執行緒的垃圾收集器,並且在執行垃圾回收的時候需要 Stop The World。虛擬機器執行在Client模式下的預設新生代收集器。Serial收集器的優點是簡單高效,對於限定在單個CPU環境來說,Serial收集器沒有多執行緒互動的開銷。
 

Serial Old收集器:

Serial Old是Serial收集器的老年代版本,也是一個單執行緒收集器。主要也是給在Client模式下的虛擬機器使用。在Server模式下存在主要是做為CMS垃圾收集器的後備預案,當CMS併發收集發生Concurrent Mode Failure時使用。
 

ParNew收集器:

ParNew是Serial收集器的多執行緒版本,新生代是並行的(多執行緒的),老年代是序列的(單執行緒的),新生代採用複製演算法,老年代採用標記整理演算法。可以使用引數:-XX:UseParNewGC使用該收集器,使用 -XX:ParallelGCThreads可以限制執行緒數量。
 

Parallel Scavenge垃圾收集器:

Parallel Scavenge是一種新生代收集器,使用複製演算法的收集器,而且是並行的多執行緒收集器。Paralle收集器特點是更加關注吞吐量(吞吐量就是cpu用於執行使用者程式碼的時間與cpu總消耗時間的比值)。可以通過-XX:MaxGCPauseMillis引數控制最大垃圾收集停頓時間;通過-XX:GCTimeRatio引數直接設定吞吐量大小;通過-XX:+UseAdaptiveSizePolicy引數可以開啟GC自適應調節策略,該引數開啟之後虛擬機器會根據系統的執行情況收集效能監控資訊,動態調整虛擬機器引數以提供最合適的停頓時間或者最大的吞吐量。自適應調節策略是Parallel Scavenge收集器和ParNew的主要區別之一。
 

Parallel Old收集器:

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

CMS(Concurrent Mark Sweep)收集器:

CMS收集器是一種以獲取最短回收停頓時間為目標的收集器。CMS收集器是基於標記-清除演算法實現的,是一種老年代收集器,通常與ParNew一起使用。

CMS的垃圾收集過程分為4步:

  • 初始標記:需要“Stop the World”,初始標記僅僅只是標記一下GC Root能直接關聯到的物件,速度很快。
  • 併發標記:是主要標記過程,這個標記過程是和使用者執行緒併發執行的。
  • 重新標記:需要“Stop the World”,為了修正併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分物件的標記記錄(停頓時間比初始標記長,但比並發標記短得多)。
  • 併發清除:和使用者執行緒併發執行的,基於標記結果來清理物件。

圖片說明

那麼問題來了,如果在重新標記之前剛好發生了一次MinorGC,會不會導致重新標記階段Stop the World時間太長?

答:不會的,在併發標記階段其實還包括了一次併發的預清理階段,虛擬機器會主動等待年輕代發生垃圾回收,這樣可以將重新標記物件引用關係的步驟放在併發標記階段,有效降低重新標記階段Stop The World的時間。
 

CMS垃圾回收器的優缺點分析:

CMS以降低垃圾回收的停頓時間為目的,很顯然其具有併發收集,停頓時間低的優點。
 

缺點主要包括如下:

  • 對CPU資源非常敏感,因為併發標記和併發清理階段和使用者執行緒一起執行,當CPU數變小時,效能容易出現問題。
  • 收集過程中會產生浮動垃圾,所以不可以在老年代記憶體不夠用了才進行垃圾回收,必須提前進行垃圾收集。通過引數-XX:CMSInitiatingOccupancyFraction的值來控制記憶體使用百分比。如果該值設定的太高,那麼在CMS執行期間預留的記憶體可能無法滿足程式所需,會出現Concurrent Mode Failure失敗,之後會臨時使用Serial Old收集器做為老年代收集器,會產生更長時間的停頓。
  • 標記-清除方式會產生記憶體碎片,可以使用引數-XX:UseCMSCompactAtFullCollection來控制是否開啟記憶體整理(無法併發,預設是開啟的)。引數-XX:CMSFullGCsBeforeCompaction用於設定執行多少次不壓縮的Full GC後進行一次帶壓縮的記憶體碎片整理(預設值是0)。

 

接下來,我們先看下上邊介紹的浮動垃圾是怎麼產生的吧。

浮動垃圾:

由於在應用執行的同時進行垃圾回收,所以有些垃圾可能在垃圾回收進行完成時產生,這樣就造成了“Floating Garbage”,這些垃圾需要在下次垃圾回收週期時才能回收掉。所以,併發收集器一般需要20%的預留空間用於這些浮動垃圾。
 

G1(Garbage-First)收集器:

G1收集器將新生代和老年代取消了,取而代之的是將堆劃分為若干的區域,仍然屬於分代收集器,區域的一部分包含新生代,新生代採用複製演算法,老年代採用標記-整理演算法。

通過將JVM堆分為一個個的區域(region),G1收集器可以避免在Java堆中進行全區域的垃圾收集。G1跟蹤各個region裡面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次根據回收時間來優先回收價值最大的region。
 

G1收集器的特點:

  • 並行與併發:G1能充分利用多CPU,多核環境下的硬體優勢,來縮短Stop the World,是併發的收集器。
  • 分代收集:G1不需要其他收集器就能獨立管理整個GC堆,能夠採用不同的方式去處理新建物件、存活一段時間的物件和熬過多次GC的物件。
  • 空間整合:G1從整體來看是基於標記-整理演算法,從區域性(兩個Region)上看基於複製演算法實現,G1運作期間不會產生記憶體空間碎片。
  • 可預測的停頓:能夠建立可以預測的停頓時間模型,預測停頓時間。

 

和CMS收集器類似,G1收集器的垃圾回收工作也分為了四個階段:

  • 初始標記
  • 併發標記
  • 最終標記
  • 篩選回收

其中,篩選回收階段首先對各個Region的回收價值和成本進行計算,根據使用者期望的GC停頓時間來制定回收計劃。
 

解析:

這塊關於垃圾回收器的知識點相對較多,我們重點介紹了CMS垃圾收集器,並且CMS也確實是我們服務中最常使用的垃圾收集器。利用CMS併發標記和清理的特性,可以有效降低使用者的停頓時間,對於服務的穩定性有一個非常顯著的提升。通常,作者本人設定的JVM啟動引數如下,學習了上邊的內容,聰明的你一定可以明白設定的含義啦。

複製程式碼

1

JAVA_OPTS="-Xms4096m –Xmx4096m -XX:NewRatio=2 -XX:SurvivorRatio=8 -Xloggc:/home/work/log/serviceName/gc.log -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=10 "

 

總結:

本小節在上一小節JVM相關基礎技術的基礎上,重點介紹了垃圾回收演算法和垃圾收集器的實現。垃圾收集器需要重點掌握CMS垃圾收集器,面試中可以準確闡述CMS相關知識點是一個亮點,顯示出自己對JVM記憶體相關技術的熟練掌握。下一小節,我們將闡述JVM相關的記憶體調優命令,並且給出如何排查線上服務故障的步驟,並且還會介紹類載入機制等相關技術原理。

相關文章