# 前言
在 深入淺出 JVM GC(2) 中孽惰,我們介紹了一些 GC 算法,GC 名詞碑隆,同時也留下了一個問題恭陡,就是每個 GC 收集器的具體作用。有哪些 GC 收集器呢上煤?
- Serial 串行收集器(只適用于堆內(nèi)存 256M 以下的 JVM )
- ParNew 并行收集器(Serial 收集器的多線程版本)
- Parallel Scavenge (PS 收集器子姜,該收集器以吞吐量為主要目的,是1.8的默認 GC)
- CMS 收集器(該收集器全稱 Concurrent Mark Sweep楼入,是一種關(guān)注最短停頓時間的垃圾收集器)
- G1 收集器(JDK 9 的默認 GC)
再回顧一下我們的那張圖吧:
下面我們就一一介紹這些垃圾收集器吧哥捕!
1. Serial 串行收集器(只適用于堆內(nèi)存256m 以下的 JVM )
什么是串行收集器呢?
串行收集器是指使用單線程進行垃圾回收的回收器嘉熊。每次回收時遥赚,串行收集器只有一個工作線程,對于并行能力較弱的計算機來說阐肤,串行回收器的專注性和獨占性往往有更好的性能表現(xiàn)凫佛。串行回收器可以在新生代和老年代使用,根據(jù)作用于不同的堆空間孕惜,分為新生代串行回收器和老年代串行回收器愧薛。
串行回收器可以說是最古老的垃圾回收器了,主要由2個特點:
- 他僅僅使用單線程進行垃圾回收衫画。
- 他是獨占式的垃圾回收器毫炉。
什么是獨占式呢?
在串行收集器金進行垃圾回收時削罩,Java 應(yīng)用程序中的線程都要暫停瞄勾,等待垃圾回收的完成费奸。這種現(xiàn)象稱之為 “Stop-The-World”,他將造成非常糟糕的用戶體驗进陡,在實時性較高的應(yīng)用場景中愿阐,這種現(xiàn)象往往是不能接受的。
即便如此趾疚,串行回收期卻是一個成熟且經(jīng)過長時間生產(chǎn)環(huán)境考驗的極為高效的收集器缨历。新生代串行收集器使用復(fù)制算法,實現(xiàn)相對簡單糙麦,且沒有線程切換的開銷戈二。在單 CPU 環(huán)境下性能表現(xiàn)良好。
我們可以使用 -XX:UseSerialGC 參數(shù)喳资,指定使用新生代串行收集器和老年代串行收集器觉吭。注意,當(dāng)虛擬機在 client 模式下仆邓,它是默認的垃圾收集器鲜滩。
當(dāng)然還有老年代串行收集器。
老年代串行收集器使用的標記壓縮算法节值,也是一個獨占式的單線程的垃圾收集器徙硅。由于老年代的垃圾回收通常比新生代垃圾回收需要更多的時間,因此搞疗,一旦老年代垃圾回收期啟動嗓蘑,系統(tǒng)將停頓很長時間。
即便如此匿乃,Serial 老年代處理器也是大名鼎鼎的 CMS 處理器的備用處理器桩皿。
2. ParNew 并行收集器(Serial 收集器的多線程版本)
上面我們說 Serial 是單線程的處理器,在單核 CPU 情況下幢炸,Serial 是個不錯的選擇泄隔,但現(xiàn)代計算機普遍都是多核,因此需要并行的處理器宛徊。
ParNew 就是 Serial 的并行版本佛嬉。多個線程同時回收,有效縮短垃圾回收所需要的實際時間闸天。
ParNew 是一個工作在新生代的垃圾收集器暖呕,他只是簡單的將串行收集器多線程化,他的回收策略苞氮,算法以及參數(shù)和新生代串行收集器是相同的湾揽。同時也是獨占式的收集器,在收集過程總,應(yīng)用會全部暫停钝腺。但由于并行回收期用多線程回收抛姑,因此赞厕,在并發(fā)能力比較強的 CPU 上艳狐,他產(chǎn)生的停頓時間要短于串行回收器。反之皿桑,如果 CPU 并行能力弱毫目,不如使用串行收集器。
同時诲侮,既然是多線程的镀虐,虛擬機給我們提供了指定線程數(shù)量的參數(shù) -XX:ParallelGCThreads,一般沟绪,最好和 CPU 數(shù)量相當(dāng)刮便,默認情況下,當(dāng) CPU 數(shù)量小于8绽慈,ParallelGCThreads 等于 CPU 數(shù)量恨旱,當(dāng) CPU 數(shù)量大于 8時,公式是: 3 + ((5 * CPU——Count)/8)坝疼。
3. Parallel Scavenge (PS 收集器搜贤,該收集器以吞吐量為主要目的,是1.8的默認 GC)
Parallel Scavenge 收集器钝凶,又稱 PS 收集器仪芒,也是多線程的,和 ParNew 類似耕陷,但是掂名,PS 收集器更關(guān)注吞吐量。
因此哟沫,PS 處理器特意提供了連個參數(shù)用于設(shè)置吞吐量相關(guān)铆隘。
-XX:MaxGCPauseMillis :設(shè)置最大垃圾收集停頓時間,他的值是一個大于0的整數(shù)南用,ParallelGC 在工作時膀钠,會調(diào)整 Java 堆大小或者其他一些參數(shù),盡可能的把停頓時間控制在 XX:MaxGCPauseMillis 以內(nèi)裹虫。如果設(shè)置的很小肿嘲,對應(yīng)的,PS 收集器會將堆設(shè)置的很兄(小堆比大堆回收快)雳窟,導(dǎo)致垃圾回收變得頻繁,從而降低了吞吐量。
-XX:GCTimeRatio: 設(shè)置吞吐量大小封救,他的值是一個0 - 100 之間的整數(shù)拇涤,假設(shè) GCTimeRatio 的值為 n,那么系統(tǒng)將花費不超過 1/(1+n)的時間用于垃圾收集誉结,比如 n 是 19鹅士,則系統(tǒng)用于垃圾收集的時間不超過 1/(1+19) = 5%的時間用于垃圾收集,默認情況下惩坑,取值為99掉盅,即不超過 1% 的時間用于垃圾收集。
注意:PS 收集器是一個自適應(yīng)的收集器以舒,使用 -XX:UseAdaptiveSizePolicy 可以打開自適應(yīng) GC 策略趾痘。在這種模式下,新生代的大小蔓钟,eden 和 Survivor 的比例永票,晉升老年代的年齡閾值將會別自動調(diào)整
,以達到在堆大小滥沫,吞吐量和停頓時間的平衡點侣集。在手工調(diào)優(yōu)比較困難的場合,可以直接使用這種自適應(yīng)的方式佣谐,僅指定虛擬機的最大堆肚吏,目標吞吐量(GCTimeTatio)和停頓時間(MaxGCPauseMillis),讓虛擬機自己完成調(diào)優(yōu)工作狭魂。
也許大家也看到了罚攀,GCTimeRatio 和 MaxGCPauseMillis 兩個參數(shù)有沖突的,通常如果減少一次垃圾收集的停頓時間雌澄,意味著你的吞吐量就會下降斋泄,如果吞吐量設(shè)置的很高,那么你的垃圾收集停頓時間又會變大镐牺。
Parallel Old
有新生代 Parallel Scavenge 收集器炫掐,也有老年代 Parallel Old 收集器,他也是一種關(guān)注吞吐量的垃圾收集器睬涧。故名思意募胃,他是一種工作在 Old 區(qū)的垃圾收集器,并且和 Parallel Scavenge 一起使用畦浓。Parallel Old 收集器使用的標記壓縮算法痹束。
4. CMS 收集器(該收集器全稱 Concurrent Mark Sweep,是一種關(guān)注最短停頓時間的垃圾收集器)
我們上面說 Parallel Scavenge 和 Parallel Old 收集器都是關(guān)注吞吐量的讶请,而現(xiàn)在說的 CMS 處理器則是關(guān)注停頓時間的祷嘶。CMS 是 Concurrent Mark Sweep 的縮寫,意味并發(fā)標記清除,從名稱上可以得知论巍,他使用的是標記清除算法(缺點是產(chǎn)生內(nèi)存碎片)烛谊,同時他又是一個使用多線程并行回收的垃圾收集器。
相對于 Serial Old, Parallel Old 這兩個老年代處理器嘉汰,CMS 比較復(fù)雜丹禀,為了實現(xiàn)更短的停頓時間,將 GC 的流程更加的細化郑现。
我們仔細思考湃崩,GC 有標記和清理兩個過程荧降,事實上接箫,清理的過程是不要 STW(Stop-The-World)的,只有在標記的時候朵诫,需要暫停所有應(yīng)用線程辛友,防止引用關(guān)系更改。因此 CMS 做了如下的設(shè)計:
上圖有6個步驟剪返,但大部分書中都是4個步驟废累,也就是綠色方框中的,注意脱盲,其中初始標記和重新標記都是要系統(tǒng)停頓的邑滨,而并發(fā)標記和重并發(fā)清理都是和系統(tǒng)應(yīng)用程序并發(fā)執(zhí)行的,因此钱反,相對于上面的兩個收集器掖看,CMS 收集器的停頓時間要小的多。
那么我們就詳細說說這幾個步驟面哥。
- 初始標記哎壳,初始標記僅僅是標記一下 GC Roots 能直接關(guān)聯(lián)到的對象,速度很快尚卫。
- 并發(fā)標記階段就是進行 GC Roots 的跟蹤過程归榕。
- 預(yù)處理,由于并發(fā)標記階段是和應(yīng)用程序并發(fā)執(zhí)行的吱涉,因此刹泄,極有可能會產(chǎn)生大量新生的對象指向老年代的對象,引用關(guān)系發(fā)生變化怎爵,同時特石,后續(xù)的 remark 階段是獨占式的,如果不處理那些新生對象和老年代對象的關(guān)系疙咸,那么 remark 階段將非常耗時县匠,嚴重影響性能。因此,在預(yù)處理階段乞旦,將會盡量處理那些變化的老年代對象贼穆,默認5秒之內(nèi),在這段時間內(nèi)兰粉,CMS 會盡量處理那些變化的對象故痊,特別是新生代中的對象,其實這5秒玖姑,實際上是在等待一次 YGC愕秫,希望 YGC 能夠把那些新生的對象消除,避免后面的 remark 階段掃描導(dǎo)致長時間暫停焰络。不過戴甩,這個功能可以通過 -XX:-CMSPrecleaningenabled 關(guān)閉。當(dāng)然也可以通過參數(shù) CMSScavengeBeforeRemark 強制在此階段發(fā)生 YGC闪彼。注意:虛擬機還會預(yù)估下次的 YGC 發(fā)生時間甜孤,盡量不讓 remark 階段和下一次 YGC 階段重疊,防止停頓時間過長畏腕。
- 重新標記缴川,為了修正并發(fā)標記期間因用戶線程繼續(xù)運作而導(dǎo)致標記產(chǎn)生變動的那一部分對象的標記記錄。這個階段的暫停時間一般會比初始標記時間稍長一些描馅,但遠比并發(fā)標記的時間短把夸。
- 并發(fā)清理,沒啥說的铭污。
- 重置之前的狀態(tài)恋日。
可以說 CMS 還是比之前的稍微的復(fù)雜了一點。同時况凉,CMS 還有3個地方需要注意:
- CMS 對 CPU 資源敏感谚鄙,什么意思呢?由于 CMS 是并發(fā)執(zhí)行的刁绒,雖然不會導(dǎo)致應(yīng)用程序暫闷营,但是會搶奪 CPU 的資源,應(yīng)用程序的性能會受到影響知市。默認線程數(shù)是 (CPU + 3)/ 4傻盟。所以需要妥當(dāng)設(shè)定好 ParallelGCThreads 參數(shù)。
- 由于并發(fā)清理階段程序會繼續(xù)運行嫂丙,會產(chǎn)生大量的對象娘赴,如果內(nèi)存不夠,將會出現(xiàn) Concurrent Mode Failure 同時 Full GC跟啤,并使用備用收集器 Serial 诽表,停頓時間將會非常的長唉锌。當(dāng)出現(xiàn)這種情況的時候,使用 -XX:CMSInitiatingOccupancyFraction 的值來設(shè)定老年代的空間使用的百分率來觸發(fā) CMS竿奏,如果 Old 區(qū)內(nèi)存增長很快袄简,則設(shè)置的低一些,防止 Full GC泛啸,反之绿语,則可以設(shè)置的高一些,盡量減少Old GC候址。
- 由于 CMS 基于標記清除算法吕粹,肯定會有內(nèi)存碎片,因此虛擬機提供了 -XX:+UseCMSCompactAtFullCollection 開關(guān)參數(shù)(默認開啟),用于在 CMS 頂不住要進行 FGC 的最后進行碎片整理岗仑,但停頓時間會變長匹耕,因此,虛擬機還提供了一個參數(shù) -XX:CMSFullGCsBeforeCompaction 赔蒲,這個參數(shù)是用于設(shè)置執(zhí)行了多少次不壓縮的 FGC 后泌神,跟著來一次整理的(默認是0良漱,也就是每次都整理)舞虱。
5. G1 收集器(Garbage-First,JDK 9 的默認 GC)
G1 遠比 CMS 復(fù)雜母市。
G1 收集器是 Java9 的默認收集器矾兜,Oracle 聲稱 G1 將會替代 CMS。為什么叫 G1 呢患久,G1 全稱 Garbage-First椅寺,也就是垃圾優(yōu)先。這和他的回收策略有關(guān)蒋失。我們慢慢往下看返帕。
G1有5個特點:
- 并行性,G1在回收期間篙挽,可以讓多個線程同時工作荆萤,這點其實上述幾個收集器都可以(除了 Serial)。
- 并發(fā)性铣卡,G1 擁有和CMS相同的作用链韭,也就是和應(yīng)用程序部分并發(fā)執(zhí)行。
- 分代 GC煮落,G1 最大的區(qū)別就是他既工作在年輕代和工作在老年代敞峭,和之前的 GC 收集器完全不同。
- 空間整理蝉仇,我們上面說 CMS 有一個缺點是內(nèi)存碎片旋讹,雖然可以通過一些參數(shù)解決殖蚕,但還是不夠完美,而 G1 從某種角度看不是基于標記清除算法沉迹,而是基于復(fù)制算法嫌褪。因此不會產(chǎn)生碎片。
- 可預(yù)測的停頓胚股,由于分區(qū)的原因笼痛,G1可以只選取部分區(qū)域進行內(nèi)存回收,縮小了范圍琅拌,相應(yīng)的減少了系統(tǒng)停頓缨伊。
那么 G1 到底是怎么做到這些的呢?
在 G1之前进宝,垃圾收集器的工作范圍都是整個新生代或者老年代刻坊,但它不是。它的內(nèi)存布局和其他收集器不同党晋,它將整個 Java 堆分為多個大小相等的獨立區(qū)域(Region)谭胚,雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離的了未玻,他們都是一部分 Region 的集合灾而。
G1 只所以可以預(yù)測停頓時間,是因為它不再像別的收集器那樣收集整個新生代或者老年代扳剿,而是回收一部分 Region旁趟。
G1 跟蹤各個 Region 里面的垃圾的價值大小(回收所獲得空間大小以及回收所需時間的經(jīng)驗值)庇绽,在后臺維護一個優(yōu)先列表锡搜,每次根據(jù)允許的收集時間,優(yōu)先回收價值最大的 Region(這也是 Garbage-First的由來)瞧掺。這種根據(jù)垃圾價值來回收 Region 的方式保證了 G1在有限的時間里回收更多的內(nèi)存耕餐。
這其實就是“化整為零”。
在 JVM 啟動時不需要立即指定哪些 Region 屬于年輕代辟狈,哪些 Region 屬于老年代肠缔,因為無論是年輕代還是老年代,他們都不需要一大塊連續(xù)的內(nèi)存上陕,只是由一系列 Region 組成而已桩砰。隨著時間的流逝,Region 有時屬于新生代释簿,有時屬于老年代亚隅,來回變動。例如開始的時候庶溶,Region A 被分配給年輕代煮纵,一個年輕代回收結(jié)束后懂鸵,這個 Region 又被放回了空閑/可用Region 隊列,可能下一次就被分配給了一個老年代對象使用行疏。
但是一切并不是那么容易匆光。
Region 不可能是孤立的,一個對象分配在某個 Region 中酿联,他并非只能被本 Region 中的其他對象引用终息,而是可以與整個 Java 堆任意的對象發(fā)生引用關(guān)系。在做可達性判斷的時候贞让,難道要掃描整個堆嗎周崭?也就是說,如果回收新生代的時候同時也掃描老年代喳张,那么 YGC 的效率將會大打折扣续镇。
G1 如何處理這個問題的呢?Region 之間的對象引用以及其他收集器中的新生代和老年代之間的對象引用销部,JVM 都是使用 Remembered Set 來避免全堆掃描的摸航。G1 中每個 Region 都有一個與之對應(yīng)的 Remembered Set。當(dāng)Region 中的引用發(fā)生變化的時候舅桩,G1 將會把這些信息記錄到一個 CardTable 數(shù)據(jù)結(jié)構(gòu)中并存到被引用對象所屬Region 的 RSet 中酱虎。當(dāng)進行內(nèi)存回收哦時,在 GC 根節(jié)點的枚舉范圍中加入 RSet 即可保證不對全堆掃描也不會有遺漏江咳。一般來說逢净,RSet 的大小占整個 Java 堆空間的1% - 20%。
G1 把整個 Java 堆劃分成若干個Region歼指,每個 Region 大小為2的倍數(shù),范圍在 1MB-32MB 之間甥雕,可能是1MB踩身,2MB,4MB社露,8MB挟阻,16MB,32MB峭弟。所有的 Region 有一樣的大小附鸽,最多可以有 2048 個 Region,在 JVM 生命周期內(nèi)都不會改變瞒瘸。
# G1 的收集過程
G1 收集過程分為4個階段:
- 新生代 GC坷备。
- 并發(fā)標記周期
- 混合收集
- 如果需要,將進行 FGC
G1 YGC 的過程和之前的YGC 基本相同:當(dāng) Eden 區(qū)占滿情臭,YGC 就會啟動省撑,YGC 只處理 Eden 和 Survivor 區(qū)赌蔑,回收后,所有的 Eden 區(qū)都應(yīng)該被清空竟秫,而 Survivor 區(qū)會被收集一部分數(shù)據(jù)娃惯,但是應(yīng)用至少仍然存在一個 Survivor 區(qū)。另外肥败,老年代的 Region 會增多趾浅,因為通常YGC 后會有大量的對象晉升到老年代。
當(dāng)老年代的使用率達到了一定的閾值馒稍,則會觸發(fā)并發(fā)標記潮孽,而并發(fā)標記的主要目的則是為了標記出那些垃圾比例較高的 Region,為后面的混合收集服務(wù)筷黔,即收集整個新生代和部分老年代往史。而并發(fā)標記的過程和 CMS 相似》鸩眨可以參考 CMS 的過程椎例。
在之前的并發(fā)標記過程中,已經(jīng)標記出來垃圾比例較高的 Region请祖,此時輪到混合回收出場了订歪,而這也是 G1 的由來,Garbage First 肆捕,優(yōu)先回收垃圾比例較高的 Region刷晋。之所以叫混合回收,是因為既執(zhí)行正常的年輕代 GC慎陵,又會選取一些被標記的老年代 Region 進行回收眼虱。被清理的區(qū)域中的存貨對象會被拷貝到其他區(qū)域,消除了 CMS 產(chǎn)生的內(nèi)存碎片席纽。
混合 GC 會執(zhí)行多次捏悬,直到回收了足夠多的內(nèi)存空間,然后润梯,他會觸發(fā)一次 YGC过牙,YGC 后,又可能會發(fā)生一次并發(fā)周期的處理纺铭,最后寇钉,又會引起混合 GC 的執(zhí)行,循環(huán)反復(fù)舶赔。如圖所示:
如果內(nèi)存增長的很快扫倡,而混合 GC 的速度又跟不上,老年代被填滿顿痪,則進行一次FGC镊辕。而 G1 和 FGC 算法是單線程的 Serial GC油够,因此會造成長時間的停頓,所以征懈,一定要避免 FGC 出現(xiàn)石咬。
# 什么時候使用 G1?
如果一個應(yīng)用程序員具有如下特征卖哎,那么將 CMS 或 ParallelOldGC 切換到G1將會大大提高性能鬼悠。否則還請繼續(xù)使用 CMS。
- Full GC 次數(shù)太頻繁或者消耗時間太長亏娜。
- 對象分配的頻率或代數(shù)(promotion)顯著變化焕窝。
- 受夠了太長的垃圾回收或內(nèi)存整理時間(超過0.5s-1s)。
#總結(jié)
到這里维贺,我們的5個垃圾收集器大致也就介紹完了它掂。注意,我們這里只是一些 概念性的介紹溯泣,甚至沒有貼出 GC 日志和大家一起分析虐秋。但 GC 的調(diào)優(yōu)是一門藝術(shù),需要不斷的試錯垃沦,才能針對當(dāng)前的應(yīng)用找到一個完美的配置客给,什么是完美的配置?YGC 時間盡量短肢簿,F(xiàn)GC 盡量沒有靶剑,如果有 CMS 或者 G1,盡量保證停頓時間盡可能的短池充。
我們將會在后面的文章繼續(xù)學(xué)習(xí) GC 的只是桩引,這里只是拋磚引玉。如果有不對的地方纵菌,還請指出阐污。感謝!
good luck!!!