GC演算法和收集器

參考:周志明《深入理解java虛擬機器》第二版

如何判斷物件可以被回收

堆中幾乎放著所有的物件例項,對堆垃圾回收前的第一步就是要判斷哪些物件已經死亡(即不能再被任何途徑使用的物件)

引用計數法

給物件新增一個引用計數器,每當有一個地方引用,計數器就加1。當引用失效,計數器就減1。任何時候計數器為0的物件就是不可能再被使用的。

這個方法實現簡單,效率高,但是目前主流的虛擬機器中沒有選擇這個演算法來管理記憶體,最主要的原因是它很難解決物件之前相互迴圈引用的問題。所謂物件之間的相互引用問題,透過下面程式碼所示:除了物件a和b相互引用著對方之外,這兩個物件之間再無任何引用。但是它們因為互相引用對方,導致它們的引用計數器都不為0,於是引用計數器法無法通知GC回收器回收它們。

GC演算法和收集器

可達性分析演算法

這個演算法的基本思想就是透過一系列的稱為”GC Roots“的物件作為起點,從這些節點開始向下搜尋,節點所走過的路徑稱為引用鏈,當一個物件到GC Roots沒有任何引用鏈相連的話,則證明此物件是不可用的。

GC Roots根節點:類載入器、Thread、虛擬機器棧的本地變量表、static成員、常量引用、本地方法棧的變數等等

GC演算法和收集器

如何判斷一個常量是廢棄常量

執行時常量池主要回收的是廢棄的常量。那麼,我們怎麼判斷一個常量是廢棄常量呢?

假如在常量池中存在字串“abc”,如果當前沒有任何String物件引用該字串常量的話,就說明常量”abc“就是廢棄常量,如果這時發生記憶體回收的話而且有必要的話,”abc“會被系統清理出常量池。

如何判斷一個類是無用的類

需要滿足以下三個條件:

該類所有的例項都已經被回收,也就是 Java 堆中不存在該類的任何例項。

載入該類的 ClassLoader 已經被回收。

該類對應的 java。lang。Class 物件沒有在任何地方被引用,無法在任何地方透過反射訪問該類的方法。

虛擬機器可以對滿足上述3個條件的無用類進行回收,這裡僅僅是”可以“,而並不是和物件一樣不適用了就必然會被回收。

垃圾回收演算法

GC演算法和收集器

標記-清除演算法

它是最基礎的收集演算法,這個演算法分為兩個階段,“標記”和”清除“。首先標記出所有需要回收的物件,在標記完成後統一回收所有被標記的物件。它有兩個不足的地方:

效率問題,標記和清除兩個過程的效率都不高;

空間問題,標記清除後會產生大量不連續的碎片

GC演算法和收集器

複製演算法

為了解決效率問題,複製演算法出現了。它可以把記憶體分為大小相同的兩塊,每次只使用其中的一塊。當這一塊的記憶體使用完後,就將還存活的物件複製到另一塊區,然後再把使用的空間一次清理掉。這樣就使每次的記憶體回收都是對記憶體區間的一半進行回收

GC演算法和收集器

標記-整理演算法

根據老年代的特點提出的一種標記演算法,標記過程和“標記-清除”演算法一樣,但是後續步驟不是直接對可回收物件進行回收,而是讓所有存活的物件向一段移動,然後直接清理掉邊界以外的記憶體

GC演算法和收集器

分代收集演算法

現在的商用虛擬機器的垃圾收集器基本都採用“分代收集”演算法,這種演算法就是根據物件存活週期的不同將記憶體分為幾塊。一般將java堆分為新生代和老年代,這樣我們就可以根據各個年代的特點選擇合適的垃圾收集演算法。

在新生代中,每次收集都有大量物件死去,所以可以選擇複製演算法,只要付出少量物件的複製成本就可以完成每次垃圾收集。而老年代的物件存活機率是比較高的,而且沒有額外的空間對它進行分配擔保,就必須選擇“標記-清除”或者“標記-整理”演算法進行垃圾收集

垃圾收集器

java虛擬機器規範對垃圾收集器應該如何實現沒有任何規定,因為沒有所謂最好的垃圾收集器出現,更不會有萬金油垃圾收集器,只能是根據具體的應用場景選擇合適的垃圾收集器。

GC演算法和收集器

Serial收集器

Serial(序列)收集器收集器是最基本、歷史最悠久的垃圾收集器了。大家看名字就知道這個收集器是一個單執行緒收集器了。它的 “單執行緒” 的意義不僅僅意味著它只會使用一條垃圾收集執行緒去完成垃圾收集工作,更重要的是它在進行垃圾收集工作的時候必須暫停其他所有的工作執行緒( “Stop The World” ),直到它收集結束。

新生代採用複製演算法,老年代採用標記-整理演算法。

GC演算法和收集器

虛擬機器的設計者們當然知道Stop The World帶來的不良使用者體驗,所以在後續的垃圾收集器設計中停頓時間在不斷縮短(仍然還有停頓,尋找最優秀的垃圾收集器的過程仍然在繼續)。

但是Serial收集器有沒有優於其他垃圾收集器的地方呢?當然有,它簡單而高效(與其他收集器的單執行緒相比)。Serial收集器由於沒有執行緒互動的開銷,自然可以獲得很高的單執行緒收集效率。Serial收集器

對於執行在Client模式下的虛擬機器來說是個不錯的選擇。

ParNew收集器

ParNew收集器其實就是Serial收集器的多執行緒版本,除了使用多執行緒進行垃圾收集外,其餘行為(控制引數、收集演算法、回收策略等等)和Serial收集器完全一樣。

新生代採用複製演算法,老年代採用標記-整理演算法

GC演算法和收集器

它是許多執行在Server模式下的虛擬機器的首要選擇,除了Serial收集器外,只有它能與CMS收集器(真正意義上的併發收集器,後面會介紹到)配合工作。

Parallel Scavenge收集器

Parallel Scavenge 收集器類似於ParNew 收集器。

Parallel Scavenge收集器關注點是吞吐量(高效率地利用CPU)。CMS等垃圾收集器的關注點更多的是使用者執行緒的停頓時間(提高使用者體驗)。所謂吞吐量就是CPU中用於執行使用者程式碼的時間與CPU總消耗時間的比值。 ParallelScavenge收集器提供了很多引數供使用者找到最合適的停頓時間或最大吞吐量,如果對於收集器運作不太瞭解的話,手工最佳化存在的話可以選擇把記憶體管理最佳化交給虛擬機器去完成也是一個不錯的選擇。

新生代採用複製演算法,老年代採用標記-整理演算法

GC演算法和收集器

Serial Old收集器

Serial收集器的老年代版本,它同樣是一個單執行緒收集器。它主要有兩大用途:一種用途是在JDK1。5以及以前的版本中與Parallel Scavenge收集器搭配使用,另一種用途是作為CMS收集器的後備方案。

Parallel Old收集器

Parallel Scavenge收集器的老年代版本。使用多執行緒和“標記-整理”演算法。在注重吞吐量以及CPU資源的場合,都可以優先考慮 Parallel Scavenge收集器和Parallel Old收集器。

CMS收集器

並行和併發概念補充:

並行(Parallel):指多條垃圾收集執行緒並行工作,但此時使用者執行緒仍然處於等待狀態。

併發(Concurrent):指使用者執行緒與垃圾收集執行緒同時執行(但不一定是並行,可能會交替執行),使用者程式在繼續執行,而垃圾收集器執行在另一個CPU上。

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。它而非常符合在注重使用者體驗的應用上使用。

CMS(Concurrent Mark Sweep)收集器是HotSpot虛擬機器第一款真正意義上的併發收集器,它第一次實現了讓垃圾收集執行緒與使用者執行緒(基本上)同時工作。

從名字中的Mark Sweep這兩個詞可以看出,CMS收集器是一種 “標記-清除”演算法實現的,它的運作過程相比於前面幾種垃圾收集器來說更加複雜一些。整個過程分為四個步驟:

初始標記(CMS initial mark):暫停所有的其他執行緒,並記錄下直接與root相連的物件,速度很快

併發標記(CMS concurrent mark):同時開啟GC和使用者執行緒,用一個閉包結構去記錄可達物件。但在這個階段結束,這個閉包結構並不能保證包含當前所有的可達物件。因為使用者執行緒可能會不斷地更新引用域,所以GC執行緒無法保證可達性分析的實時性。所以這個演算法裡會跟蹤記錄這些發生引用更新的地方。

重新標記(CMS remark):重新標記階段就是為了修正併發標記期間因為使用者程式繼續執行而導致標記產生變動的那一部分物件的標記記錄,這個階段的停頓時間一般會比初始標記階段的時間稍長,遠遠比並發標記階段時間短並

發清除(CMS concurrent sweep):開啟使用者執行緒,同時GC執行緒開始對未標記的區域做清掃

GC演算法和收集器

CMS主要優點:併發收集、低停頓。但是它有下面三個明顯的缺點:

對CPU資源敏感;

無法處理浮動垃圾;

它使用的回收演算法-“標記-清除”演算法會導致收集結束時會有大量空間碎片產生。

G1收集器

G1 (Garbage-First)是一款面向伺服器的垃圾收集器,主要針對配備多顆處理器及大容量記憶體的機器。 以極高機率滿足GC停頓時間要求的同時,還具備高吞吐量效能特徵

GC演算法和收集器

被視為JDK1。7中HotSpot虛擬機器的一個重要進化特徵。它具備一下特點:

並行與併發:G1能充分利用CPU、多核環境下的硬體優勢,使用多個CPU(CPU或者CPU核心)來縮短Stop-The-World停頓時間。部分其他收集器原本需要停頓Java執行緒執行的GC動作,G1收集器仍然可以透過併發的方式讓java程式繼續執行

分代收集:雖然G1可以不需要其他收集器配合就能獨立管理整個GC堆,但是還是保留了分代的概念。空間整合:與CMS的“標記–清理”演算法不同,G1從整體來看是基於“標記整理”演算法實現的收集器;從區域性上來看是基於“複製”演算法實現的

可預測的停頓:這是G1相對於CMS的另一個大優勢,降低停頓時間是G1 和 CMS 共同的關注點,但G1 除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為M毫秒的時間片段內

G1收集器的運作大致分為以下幾個步驟:

初始標記

併發標記

最終標記

篩選回收

G1收集器在後臺維護了一個優先列表,每次根據允許的收集時間,優先選擇回收價值最大的Region(這也就是它的名字Garbage-First的由來)。這種使用Region劃分記憶體空間以及有優先順序的區域回收方式,保證了GF收集器在有限時間內可以儘可能高的收集效率(把記憶體化整為零)。

怎麼選擇垃圾收集器?

1。 優先調整堆的大小讓伺服器自己來選擇

2。 如果記憶體小於100m,使用序列收集器

3。 如果是單核,並且沒有停頓時間的要求,序列或JVM自己選擇

4。 如果允許停頓時間超過1秒,選擇並行或者JVM自己選

5。 如果響應時間最重要,並且不能超過1秒,使用併發收集器

官方推薦G1,效能高。

調優

JVM調優主要就是調整下面兩個指標

停頓時間:垃圾收集器做垃圾回收中斷應用執行的時間。-XX:MaxGCPauseMillis

吞吐量:垃圾收集的時間和總時間的佔比:1/(1+n),吞吐量為1-1/(1+n)。-XX:GCTimeRatio=n

GC調優步驟

1。列印GC日誌

-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:。/gc。log

Tomcat可以直接載入JAVA_OPTS變數裡

2。分析日誌得到關鍵性指標

3。分析GC原因,調優JVM引數

1。Parallel Scavenge收集器(預設)

分析parallel-gc。log

第一次調優,設定Metaspace大小:增大元空間大小-XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=64M

第二次調優,增大年輕代動態擴容增量(預設是20%),可以減少YGC:-XX:YoungGenerationSizeIncrement=30比較下幾次調優效果

GC演算法和收集器

2。配置CMS收集器

-XX:+UseConcMarkSweepGC

分析gc-cms。log

3。配置G1收集器

young GC:[GC pause (G1 Evacuation Pause)(young)

initial-mark:[GC pause (Metadata GC Threshold)(young)(initial-mark) (引數:InitiatingHeapOccupancyPercent)

mixed GC:[GC pause (G1 Evacuation Pause)(Mixed) (引數:G1HeapWastePercent)

full GC:[Full GC (Allocation Failure)(不可用region)

(G1內部,前面提到的混合GC是非常重要的釋放記憶體機制,它避免了G1出現Region沒有可用的情況,否則就會觸發 FullGC事件。CMS、Parallel、Serial GC都需要透過Full GC去壓縮老年代並在這個過程中掃描整個老年代。G1的Full GC演算法和Serial GC收集器完全一致。當一個Full GC發生時,整個Java堆執行一個完整的壓縮,這樣確保了最大的空餘記憶體可用。G1的Full GC是一個單執行緒,它可能引起一個長時間的停頓時間,G1的設計目標是減少Full GC,滿足應用效能目標。)

檢視發生MixedGC的閾值:jinfo -flag InitiatingHeapOccupancyPercent 程序ID

調優:

第一次調優,設定Metaspace大小:增大元空間大小-XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=64M

第二次調優,新增吞吐量和停頓時間引數:-XX:GCTimeRatio=99 -XX:MaxGCPauseMillis=10

GC常用引數

堆疊設定

-Xss:每個執行緒的棧大小

-Xms:初始堆大小,預設物理記憶體的1/64

-Xmx:最大堆大小,預設物理記憶體的1/4

-Xmn:新生代大小

-XX:NewSize:設定新生代初始大小

-XX:NewRatio:預設2表示新生代佔年老代的1/2,佔整個堆記憶體的1/3。

-XX:SurvivorRatio:預設8表示一個survivor區佔用1/8的Eden記憶體,即1/10的新生代記憶體。

-XX:MetaspaceSize:設定元空間大小

-XX:MaxMetaspaceSize:設定元空間最大允許大小,預設不受限制,JVM Metaspace會進行動態擴充套件。

垃圾回收統計資訊

-XX:+PrintGC

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-Xloggc:filename

收集器設定

-XX:+UseSerialGC:設定序列收集器

-XX:+UseParallelGC:設定並行收集器

-XX:+UseParallelOldGC:老年代使用並行回收收集器

-XX:+UseParNewGC:在新生代使用並行收集器

-XX:+UseParalledlOldGC:設定並行老年代收集器

-XX:+UseConcMarkSweepGC:設定CMS併發收集器

-XX:+UseG1GC:設定G1收集器

-XX:ParallelGCThreads:設定用於垃圾回收的執行緒數

並行收集器設定

-XX:ParallelGCThreads:設定並行收集器收集時使用的CPU數。並行收集執行緒數。

-XX:MaxGCPauseMillis:設定並行收集最大暫停時間

-XX:GCTimeRatio:設定垃圾回收時間佔程式執行時間的百分比。公式為1/(1+n)CMS收集器設定

-XX:+UseConcMarkSweepGC:設定

CMS併發收集器

-XX:+CMSIncrementalMode:設定為增量模式。適用於單CPU情況。

-XX:ParallelGCThreads:設定併發收集器新生代收集方式為並行收集時,使用的CPU數。並行收集執行緒數。

-XX:CMSFullGCsBeforeCompaction:設定進行多少次CMS垃圾回收後,進行一次記憶體壓縮

-XX:+CMSClassUnloadingEnabled:允許對類元資料進行回收

-XX:UseCMSInitiatingOccupancyOnly:表示只在到達閥值的時候,才進行CMS回收

-XX:+CMSIncrementalMode:設定為增量模式。適用於單CPU情況

-XX:ParallelCMSThreads:設定CMS的執行緒數量

-XX:CMSInitiatingOccupancyFraction:設定CMS收集器在老年代空間被使用多少後觸發

-XX:+UseCMSCompactAtFullCollection:設定CMS收集器在完成垃圾收集後是否要進行一次記憶體碎片的整理

G1收集器設定

-XX:+UseG1GC:使用G1收集器-XX:ParallelGCThreads:指定GC工作的執行緒數量

-XX:G1HeapRegionSize:指定分割槽大小(1MB~32MB,且必須是2的冪),預設將整堆劃分為2048個分割槽

-XX:GCTimeRatio:吞吐量大小,0-100的整數(預設9),值為n則系統將花費不超過1/(1+n)的時間用於垃圾收集

-XX:MaxGCPauseMillis:目標暫停時間(預設200ms)

-XX:G1NewSizePercent:新生代記憶體初始空間(預設整堆5%)

-XX:G1MaxNewSizePercent:新生代記憶體最大空間

-XX:TargetSurvivorRatio:Survivor填充容量(預設50%)

-XX:MaxTenuringThreshold:最大任期閾值(預設15)

-XX:InitiatingHeapOccupancyPercen:老年代佔用空間超過整對比IHOP閾值(預設45%),超過則執行混合收集

-XX:G1HeapWastePercent:堆廢物百分比(預設5%)

-XX:G1MixedGCCountTarget:引數混合週期的最大總次數(預設8)