1. 程式人生 > >JVM中的垃圾回收器

JVM中的垃圾回收器

1. 收集器概覽

Oracle Hotspot JVM中實現了多種垃圾收集器,針對不同的年齡代記憶體中的物件的生存週期和應用程式的特點,實現了多款垃圾收集器。
單執行緒GC收集器包括Serial和SerialOld這兩款收集器,分別用於年輕代和老年代的垃圾收集工作。後來,隨著CPU多核的普及,為了更好了利用多核的優勢,開發了ParNew收集器,這款收集器是Serial收集器的多執行緒版本。
多執行緒收集器還包括Parallel Scavenge和ParallelOld收集器,這兩款也分別用於年輕代和老年代的垃圾收集工作,不同的是,它們是兩款可以利用多核優勢的多執行緒收集器。
相對來說更加複雜的還有CMS收集器。這款收集器,在執行的時候會分多個階段進行垃圾收集,而且在一些階段是可以和應用執行緒並行執行的,提高了這款收集器的收集效率。
其中最先進的收集器,要數G1這款收集器了。這款收集器是當前最新發布的收集器,是一款面向服務端垃圾收集器。
接下來,我們來分別介紹下上面提到的那些GC收集器以及它們各自的特點。

2. 年輕代收集器

年輕代收集器包括Serial收集器、ParNew收集器以及Parallel Scavenge收集器。

2.1 Serial收集器

Serial收集器是一款年輕代的垃圾收集器,使用標記-複製垃圾收集演算法。它是一款發展歷史最悠久的垃圾收集器。Serial收集器只能使用一條執行緒進行垃圾收集工作,並且在進行垃圾收集的時候,所有的工作執行緒都需要停止工作,等待垃圾收集執行緒完成以後,其他執行緒才可以繼續工作。工作過程可以簡單的用下圖來表示:
在這裡插入圖片描述

從圖中可以看到,Serial收集器工作的時候,其他使用者執行緒都停止下來,等到GC過程結束以後,它們才繼續執行。而且處理GC過程的只有一條執行緒在執行。由於Serial收集器的這種工作機制,所以在進行垃圾收集過程中,會出現STW(Stop The World)

的情況,應用程式會出現停頓的狀況。如果垃圾收集的時間很長,那麼停頓時間也會很長,這樣會導致系統響應變的遲鈍,影響系統的時候。
雖然這款年邁的垃圾收集器只能使用單核CPU,但是正是由於它不能利用多核,在一些場景下,減少了很多執行緒的上下文切換的開銷,可以在進行垃圾收集過程中專心處理GC過程,而不會被打斷,所以如果GC過程很短暫,那麼這款收集器還是非常簡單高效的。
由於Serial收集器只能使用單核CPU,在現代處理器基本都是多核多執行緒的情況下,為了充分利用多核的優勢,出現了多執行緒版本的垃圾收集器,比如下面將要說到的ParNew收集器。

2.2 ParNew收集器

ParNew垃圾收集器是Serial收集器的多執行緒版本,使用標記-複製垃圾收集演算法。為了利用CPU多核多執行緒的優勢,ParNew收集器可以執行多個收集執行緒來進行垃圾收集工作。這樣可以提高垃圾收集過程的效率。
在這裡插入圖片描述


和上面的Serial收集器比較,可以明顯看到,在垃圾收集過程中,GC執行緒是多執行緒執行的,而在Serial收集器中,只有一個GC執行緒在處理垃圾收集過程。ParNew收集器在很多時候都是作為服務端的年輕代收集器的選擇,除了它具有比Serial收集器更好的效能外,還有一個原因是,多執行緒版本的年輕代收集器中,只有它可以和CMS這款優秀的老年代收集器一起搭配搭配使用。
作為一款多執行緒收集器,當它執行在單CPU的機器上的時候,由於不能利用多核的優勢,線上程收集過程中可能會出現頻繁上下文切換,導致額外的開銷,所以在單CPU的機器上,ParNew收集器的效能不一定好於Serial這款單執行緒收集器。如果機器是多CPU的,那麼ParNew還是可以很好的提高GC收集的效率的。
ParNew收集器預設開啟的垃圾收集執行緒數是和當前機器的CPU數量相同的,為了控制GC收集執行緒的數量,可以通過引數-XX:ParallelGCThreads來控制垃圾收集執行緒的數量。

2.3 Parallel Scavenge收集器

Parallel Scavenge收集器是是一款年輕代的收集器,它使用標記-複製垃圾收集演算法。和ParNew一樣,它也會一款多執行緒的垃圾收集器,但是它又和ParNew有很大的不同點。
Parallel Scavenge收集器和其他收集器的關注點不同。其他收集器,比如ParNew和CMS這些收集器,它們主要關注的是如何縮短垃圾收集的時間。而Parallel Scavenge收集器關注的是如何控制系統執行的吞吐量。這裡說的吞吐量,指的是CPU用於執行應用程式的時間和CPU總時間的佔比:
吞吐量 = 程式碼執行時間 / (程式碼執行時間 + 垃圾收集時間)
如果虛擬機器執行的總的CPU時間是100分鐘,而用於執行垃圾收集的時間為1分鐘,那麼吞吐量就是99%。
在這裡插入圖片描述
直觀上,好像以縮短垃圾收集的停頓時間為目的和以控制吞吐量為目的差不多,但是適用的場景卻不同。對於那些桌面應用程式,為了得到良好的使用者體驗,在互動過程中,需要得到快速的響應,所以系統的停頓時間要儘可能的快以避免影響到系統的響應速度,只要保證每次停頓的時間很短暫,假設每次停頓時間為10ms,那麼即使發生很多次的垃圾收集過程,假設1000次,也不會影響到系統的響應速度,不會影響到使用者的體驗。對於一些後臺計算任務,它不需要和使用者進行互動,所以短暫的停頓時間對它而言並不需要,對於計算任務而言,更好的利用CPU時間,提高計算效率才是需要的,所以假設每次停頓時間相對很長,有100ms,而由於花費了很長的時間進行垃圾收集,那麼垃圾收集的次數就會降下來,假設只有5次,那麼顯然,使用以吞吐量為目的的垃圾收集器,可以更加有效的利用CPU來完成計算任務。所以,在使用者介面程式中,使用低延遲的垃圾收集器會有很好的效果,而對於後臺計算任務的系統,高吞吐量的收集器才是首選。

Parallel Scavenge收集器提供了兩個引數用於控制吞吐量。-XX:MaxGCPauseMillis用於控制最大垃圾收集停頓時間,-XX:GCTimeRatio用於直接控制吞吐量的大小。MaxGCPauseMillis引數的值允許是一個大於0的整數,表示毫秒數,收集器會盡可能的保證每次垃圾收集耗費的時間不超過這個設定值。但是如果這個這個值設定的過小,那麼Parallel Scavenge收集器為了保證每次垃圾收集的時間不超過這個限定值,會導致垃圾收集的次數增加和增加年輕代的空間大小,垃圾收集的吞吐量也會隨之下降。GCTimeRatio這個引數的值應該是一個0-100之間的整數,表示應用程式執行時間和垃圾收集時間的比值。如果把值設定為19,即系統執行時間 : GC收集時間 = 19 : 1,那麼GC收集時間就佔用了總時間的5%(1 / (19 + 1) = 5%),該引數的預設值為99,即最大允許1%(1 / (1 + 99) = 1%)的垃圾收集時間。
Parallel Scavenge收集器還有一個引數:-XX:UseAdaptiveSizePolicy。這是一個開關引數,當開啟這個引數以後,就不需要手動指定新生代的記憶體大小(-Xmn)、Eden區和Survivor區的比值(-XX:SurvivorRatio)以及晉升到老年代的物件的大小(-XX:PretenureSizeThreshold)等引數了,虛擬機器會根據當前系統的執行情況動態調整合適的設定值來達到合適的停頓時間和合適的吞吐量,這種方式稱為GC自適應調節策略。
Parallel Scavenge收集器也是一款多執行緒收集器,但是由於目的是為了控制系統的吞吐量,所以這款收集器也被稱為吞吐量優先收集器。

3. 老年代收集器

老年代收集包括:Serial Old收集器、Parallel Old收集器以及CMS收集器。

3.1 Serial Old收集器

Serial Old收集器是Serial收集器的老年代版本,它也是一款使用標記-整理演算法的單執行緒的垃圾收集器。這款收集器主要用於客戶端應用程式中作為老年代的垃圾收集器,也可以作為服務端應用程式的垃圾收集器,當它用於服務端應用系統中的時候,主要是在JDK1.5版本之前和Parallel Scavenge年輕代收集器配合使用,或者作為CMS收集器的後備收集器。
在這裡插入圖片描述

3.2 Parallel Old收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用標記-整理演算法。這個收集器是在JDK1.6版本中出現的,所以在JDK1.6之前,新生代的Parallel Scavenge只能和Serial Old這款單執行緒的老年代收集器配合使用。Parallel Old垃圾收集器和Parallel Scavenge收集器一樣,也是一款關注吞吐量的垃圾收集器,和Parallel Scavenge收集器一起配合,可以實現對Java堆記憶體的吞吐量優先的垃圾收集策略。
Parallel Old垃圾收集器的工作原理和Parallel Scavenge收集器類似。
在這裡插入圖片描述

3.3 CMS收集器

CMS收集器是目前老年代收集器中比較優秀的垃圾收集器。CMS是Concurrent Mark Sweep,從名字可以看出,這是一款使用標記-清除演算法的併發收集器。CMS
垃圾收集器是一款以獲取最短停頓時間為目標的收集器。由於現代網際網路中的應用,比較重視服務的響應速度和系統的停頓時間,所以CMS收集器非常適合在這種場景下使用。
CMS收集器的執行過程相對上面提到的幾款收集器要複雜一些。
在這裡插入圖片描述
從圖中可以看出,CMS收集器的工作過程可以分為4個階段:

  • 初始標記(CMS initial mark)階段
  • 併發標記(CMS concurrent mark)階段
  • 重新標記(CMS remark)階段
  • 併發清除(CMS concurrent sweep)階段

從圖中可以看出,在這4個階段中,初始標記和重新標記這兩個階段都是隻有GC執行緒在執行,使用者執行緒會被停止,所以這兩個階段會發生STW(Stop The World)。初始標記階段的工作是標記GC Roots可以直接關聯到的物件,速度很快。併發標記階段,會從GC Roots 出發,標記處所有可達的物件,這個過程可能會花費相對比較長的時間,但是由於在這個階段,GC執行緒和使用者執行緒是可以一起執行的,所以即使標記過程比較耗時,也不會影響到系統的執行。重新標記階段,是對併發標記期間因使用者程式執行而導致標記變動的那部分記錄進行修正,重新標記階段耗時一般比初始標記稍長,但是遠小於併發標記階段。最終,會進行併發清理階段,和併發標記階段類似,併發清理階段不會停止系統的執行,所以即使相對耗時,也不會對系統執行產生大的影響。
由於併發標記和併發清理階段是和應用系統一起執行的,而初始標記和重新標記相對來說耗時很短,所以可以認為CMS收集器在執行過程中,是和應用程式是併發執行的。由於CMS收集器是一款併發收集和低停頓的垃圾收集器,所以CMS收集器也被稱為併發低停頓收集器。
雖然CMS收集器可以是實現低延遲併發收集,但是也存在一些不足。
首先,CMS收集器對CPU資源非常敏感。對於併發實現的收集器而言,雖然可以利用多核優勢提高垃圾收集的效率,但是由於收集器在執行過程中會佔用一部分的執行緒,這些執行緒會佔用CPU資源,所以會影響到應用系統的執行,會導致系統總的吞吐量降低。CMS預設開始的回收執行緒數是(Ncpu + 3) / 4,其中Ncpu是機器的CPU數。所以,當機器的CPU數量為4個以上的時候,垃圾回收執行緒將佔用不少於%25的CPU資源,並且隨著CPU數量的增加,垃圾回收執行緒佔用的CPU資源會減少。但是,當CPU資源少於4個的時候,垃圾回收執行緒佔用的CPU資源的比例會增大,會影響到系統的執行,假設有2個CPU的情況下,垃圾回收執行緒將會佔據超過50%的CPU資源。所以,在選用CMS收集器的時候,需要考慮,當前的應用系統,是否對CPU資源敏感。
其次,CMS收集器在處理垃圾收集的過程中,可能會產生浮動垃圾,由於它無法處理浮動垃圾,所以可能會出現Concurrent Mode Failure問題而導致觸發一次Full GC。所謂的浮動垃圾,是由於CMS收集器的併發清理階段,清理執行緒是和使用者執行緒一起執行,如果在清理過程中,使用者執行緒產生了垃圾物件,由於過了標記階段,所以這些垃圾物件就成為了浮動垃圾,CMS無法在當前垃圾收集過程中集中處理這些垃圾物件。由於這個原因,CMS收集器不能像其他收集器那樣等到完全填滿了老年代以後才進行垃圾收集,需要預留一部分空間來保證當出現浮動垃圾的時候可以有空間存放這些垃圾物件。在JDK 1.5中,預設當老年代使用了68%的時候會啟用垃圾收集,這是一個保守的設定,如果在應用中老年代增長不是很快,可以通過引數-XX:CMSInitiatingOccupancyFraction控制觸發的百分比,以便降低記憶體回收次數來提供效能。在JDK 1.6中,CMS收集器的啟用閥值變成了92%。如果在CMS執行期間沒有足夠的記憶體來存放浮動垃圾,那麼就會導致Concurrent Mode Failure失敗,這個時候,虛擬機器將啟動後備預案,臨時啟動Serial Old收集器來對老年代重新進行垃圾收集,這樣會導致垃圾收集的時間邊長,特別是當老年代記憶體很大的時候。所以對引數-XX:CMSInitiatingOccupancyFraction的設定,過高,會導致發生Concurrent Mode Failure,過低,則浪費記憶體空間。
CMS的最後一個問題,就是它在進行垃圾收集時使用的標記-清除演算法,會出現很多記憶體碎片,過多的記憶體碎片會影響大物件的分配,會導致即使老年代記憶體還有很多空閒,但是由於過多的記憶體碎片,不得不提前觸發垃圾回收。為了解決這個問題,CMS收集器提供了一個-XX:+UseCMSCompactAtFullCollection引數,用於CMS收集器在必要的時候對記憶體碎片進行壓縮整理。由於記憶體碎片整理過程不是併發的,所以會導致停頓時間變長。-XX:+UseCMSCompactAtFullCollection引數預設是開啟的。虛擬機器還提供了一個-XX:CMSFullGCsBeforeCompaction引數,來控制進行過多少次不壓縮的Full GC以後,進行一次帶壓縮的Full GC,預設值是0,表示每次在進行Full GC前都進行碎片整理。
雖然CMS收集器存在上面提到的這些問題,但是毫無疑問,CMS當前仍然是非常優秀的垃圾收集器。

4. G1收集器

G1(Garbage First)垃圾收集器是當今垃圾回收技術最前沿的成果之一。早在JDK7就已加入JVM的收集器大家庭中,成為HotSpot重點發展的垃圾回收技術。同優秀的CMS垃圾回收器一樣,G1也是關注最小時延的垃圾回收器,也同樣適合大尺寸堆記憶體的垃圾收集,官方也推薦使用G1來代替選擇CMS。G1最大的特點是引入分割槽的思路,弱化了分代的概念,合理利用垃圾收集各個週期的資源,解決了其他收集器甚至CMS的眾多缺陷。

G1收集與前面介紹的收集器有很大不同:

  • G1的設計原則是"首先收集儘可能多的垃圾(Garbage First)"。因此,G1並不會等記憶體耗盡(序列、並行)或者快耗盡(CMS)的時候開始垃圾收集,而是在內部採用了啟發式演算法,在老年代找出具有高收集收益的分割槽進行收集。同時G1可以根據使用者設定的暫停時間目標自動調整年輕代和總堆大小,暫停目標越短年輕代空間越小、總空間就越大;
  • G1採用記憶體分割槽(Region)的思路,將記憶體劃分為一個個相等大小的記憶體分割槽,回收時則以分割槽為單位進行回收,存活的物件複製到另一個空閒分割槽中。由於都是以相等大小的分割槽為單位進行操作,因此G1天然就是一種壓縮方案(區域性壓縮);
  • G1雖然也是分代收集器,但整個記憶體分割槽不存在物理上的年輕代與老年代的區別,也不需要完全獨立的survivor(to space)堆做複製準備。G1只有邏輯上的分代概念,或者說每個分割槽都可能隨G1的執行在不同代之間前後切換;
  • G1的收集都是STW的,但年輕代和老年代的收集界限比較模糊,採用了mixed gc的方式。即每次收集既可能只收集年輕代分割槽(年輕代收集),也可能在收集年輕代的同時,包含部分老年代分割槽(混合收集),這樣即使堆記憶體很大時,也可以限制收集範圍,從而降低停頓。

Region區域劃分與其他收集類似,不同的是單獨將大物件分配到了單獨的region中,會分配一組連續的Region區域(Humongous start 和 humonous Contoinue 組成),所以一共有四類Region(Eden,Survior,Humongous和Old):
在這裡插入圖片描述
G1收集器之所以能建立可預測的停頓時間模型,是因為它可以有計劃地避免在整個Java堆中進行全區域的垃圾收集。G1跟蹤各個Region裡面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的Region(這也就是Garbage-First名稱的來由)。這種使用Region劃分記憶體空間以及有優先順序的區域回收方式,保證了G1收集器在有限的時間內可以獲取儘可能高的收集效率。
G1收集器的運作大致可劃分為以下幾個步驟:

  • 初始標記(Initial Marking)
    初始標記階段僅僅只是標記一下GC Roots能直接關聯到的物件,並且修改TAMS(Next Top at Mark Start)的值,讓下一階段使用者程式併發執行時,能在正確可用的Region中建立新物件,這階段需要停頓執行緒,但耗時很短。
  • 併發標記(Concurrent Marking)
    併發標記階段是從GC Root開始對堆中物件進行可達性分析,找出存活的物件,這階段耗時較長,但可與使用者程式併發執行。
  • 最終標記(Final Marking)
    最終標記階段是為了修正在併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分標記記錄,虛擬機器將這段時間物件變化記錄線上程Remembered Set Logs裡面,最終標記階段需要把Remembered Set Logs的資料合併到Remembered Set中,這階段需要停頓執行緒,但是可並行執行。
  • 篩選回收(Live Data Counting and Evacuation)
    篩選回收階段首先對各個Region的回收價值和成本進行排序,根據使用者所期望的GC停頓時間來制定回收計劃,這個階段其實也可以做到與使用者程式一起併發執行,但是因為只回收一部分價值高的Region區的垃圾物件,時間是使用者可控制的,而且停頓使用者執行緒將大幅提高收集效率。回收時,採用“複製”演算法,從一個或多個Region複製存活物件到堆上的另一個空的Region,並且在此過程中壓縮和釋放記憶體。

G1部分設定引數如下:
-XX:+UseG1GC:指定使用G1收集器;
-XX:InitiatingHeapOccupancyPercent:當整個Java堆的佔用率達到引數值時,開始併發標記階段;預設為45;
-XX:MaxGCPauseMillis:為G1設定暫停時間目標,預設值為200毫秒;
-XX:G1HeapRegionSize:設定每個Region大小,範圍1MB到32MB;目標是在最小Java堆時可以擁有約2048個

5. 垃圾收集器總結

GC組合 Minor GC Full GC 描述
-XX:+UseSerialGC Serial收集器序列回收 Serial Old收集器序列回收 該選項可以手動指定Serial收集器+Serial Old收集器組合執行記憶體回收

6. GC日誌分析

垃圾收集器在進行垃圾收集的過程中,可以輸出日誌,我們通過日誌,可以看到當前垃圾收集器的執行情況。通過gc日誌,我們可以觀察垃圾收集器的行為,以及當前應用程式的GC情況和記憶體使用情況。學會檢視和分析垃圾收集日誌,一方面可以幫助我們學習垃圾收集器;另一方面,在必要的時候,可以幫助我們定位問題,解決問題,對JVM進行優化。
預設,JVM不會打印出GC日誌資訊,可以通過引數-XX:+PrintGC-verbose:gc來設定JVM輸出gc日誌到終端中。

JVM引數:-XX:+PrintGC -XX:+UseSerialGC -Xms10m -Xmx10m
在這裡插入圖片描述
當設定了-XX:+PrintGC或者-verbose:gc以後就會輸出類似輸出上面的GC日誌。這是最簡單的GC日誌,包含了垃圾收集過程中的資訊。其中紅色部分的"GC"和"Full GC"表示這次GC的型別,而綠色部分的"Allocation Failure"表示表示發生這次GC的原因,從上面的日誌可以看出,是由於記憶體分配失敗導致的GC。後面的黃色部分"1922K->1394K(9920K)"表示這次GC導致JVM中堆記憶體的使用量從1922K降低到了1394K,其中括號中表示當前整個JVM堆的大小。最後藍色部分的"0.0021245 secs"表示這次GC持續的時間。
上面輸出的是簡單格式的GC日誌,雖然提供了一些資訊,但是通過這些資訊,我們沒法知道這次GC發生的時候,這次GC是發生在老年代還是在年輕代,是否有物件從年輕代被移動到了老年代等資訊,所以我們希望可以看到更加詳盡的資訊。這個時候,我們需要設定-XX:+PrintGCDetails引數來輸出更加詳細的GC日誌,下面我們結合不同的收集器組合,來分析下它們的輸出日誌。

6.1 Serial GC + Serial Old

Serial GC和Serial Old收集器是比較早的單執行緒收集器,工作原理我們在上面已經介紹過了。這裡,我們來看下使用這兩款收集器進行垃圾收集的時候,輸出的日誌格式是怎麼樣的。首先我們需要設定JVM引數:

JVM引數:-XX:+PrintGC -XX:+PrintGCDetails -XX:+UseSerialGC -Xms10m -Xmx10m
在這裡插入圖片描述

可以發現,通過設定了-XX:+PrintGCDetails以後,輸出的GC日誌資訊多了很多。我們先來看第一條,紅色部分"GC"表示這次發生的是Minor GC,綠色部分"Allocation Failure"表示導致這次GC的原因是記憶體分配失敗。接下來,黃色部分的內容,則和前面的日誌有些區別了,這裡輸出的內容相對比較詳細。“DefNew: 1922K->319K(3072K), 0.0027356 secs] 1922K->1394K(9920K), 0.0027698 secs”,其中DefNew表示這次GC發生在年輕代(不同的收集器,日誌的格式不一定相同),接下來"1922K->319K"表示這次GC導致年輕代使用的記憶體從1922K降到319K,括號中的"3072K"表示年輕代中的堆記憶體大小為3072K。"0.0027356 secs"表示這次年輕代GC耗時0.0027356s。後面的"1922K->1393K"表示總的堆記憶體(年輕代 + 老年代)的使用情況的變化,從1922K降低到1394K, 括號中的"9920K"表示總的堆記憶體的大小。最後的"0.0027698 secs"表示這次GC總的消耗的時間。最後是這次GC消耗的時間的統計,其中user表示使用者態CPU執行的時間,sys表示核心態CPU執行的時間,這兩個時間不包括被掛起消耗的時間,而real表示的是實際的時間,可以認為是牆上時鐘走過的時間。
下面的這條日誌,"Full GC"表示這次GC是一次Major GC,後面的原因和上面一樣。我們來看下黃色部分,“Tenured"表示這次GC發生在老年代,其中"6524K->6484K"表示老年代記憶體從6524K降低到6484K。後面的時間"0.0025899 secs"表示這次老年代GC耗時0.0025899s。接下來的"8562K -> 8532K"和上面提到的一樣,表示整個堆記憶體的變化。最後的時間表示這次GC的總耗時為"0.0026153s”。

6.2 Parallel Scanvage + Parallel Old

不同的垃圾收集器,輸出的日誌資訊也不是完全相同的,上面我們看到的日誌,是使用Serial GC和Serial Old收集器輸出的gc日誌,而下面的日誌資訊,則是使用Parallel Scavenge收集器和Parallel Old收集器輸出的日誌。

JVM引數:-XX:+PrintGC -XX:+UseParallelOldGC -XX:+PrintGCDetails -Xms10m -Xmx10m
在這裡插入圖片描述
可以看到,使用Parallel Scavenge 和 Parallel Old收集器輸出的日誌,會有一些不同,不過日誌內容大體上差不多。最後,我們來看下CMS垃圾收集器的日誌是怎麼樣的,相對上面幾款收集器,CMS相對更加複雜,從它輸出的日誌也可以看出來。

6.3 ParNew + Concurrent Mark Sweep(CMS)

下面,我們來看下ParNew配合CMS收集器在進行垃圾收集的時候,輸出的GC 日誌資訊。

JVM引數:-XX:+PrintGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -Xms10m -Xmx10m
在這裡插入圖片描述
通過第一條日誌,可以看出我們使用"-XX:+UseConcMarkSweepGC"指定CMS垃圾收集器的時候,使用的是ParNew + CMS收集器組合。下面輸出的一堆日誌,就是CMS收集器在進行垃圾收集過程中輸出的資訊。可以明顯的看到,CMS在進行垃圾收集的過程中,經歷了4個階段,在日誌中我用4中顏色標記出來了。需要注意的是黃色部分,這是CMS的重新標記的階段,在上面我們介紹CMS收集器的時候說過,在這個階段,是會出現Stop The World的,所以如果這個階段消耗的時間比較長,則會影響應用的響應時間。

6.4 其他日誌引數

有時候,我們需要在GC日誌中輸出時間值,這樣我們就可以知道這次GC發生的具體時間點。我們可以通過JVM引數-XX:+PrintGCDateStamps來設定日誌輸出的時間。
在這裡插入圖片描述
除了將日誌輸出到控制檯,我們還可以將日誌輸出到日誌檔案中,這樣就可以通過分析日誌檔案來分析系統的GC情況了,一般在伺服器執行過程中,我們都會將GC日誌輸出到指定的檔案中,供需要的時候分析。可以通過JVM引數-Xloggc:<file>來指定日誌輸出的目錄。