Serial收集器
單線程收集器蚓聘,收集時會暫停所有工作線程(Stop The World)与纽,虛擬機運行在Client模式時的默認新生代收集器急迂。
- 最早的收集器僚碎,單線程進行GC
- New和Old Generation 都可以使用
- 在新生代,采用復制算法那皆看;在老年代腰吟,采用Mark-Compact算法
- 因為是單線程GC, 沒有多線程切換的額外開銷嫉称,簡單使用
-
Hotspot Client模式缺省的收集器
image.png
ParNew收集器
ParNew收集器就是Serial的多線程版本织阅,除了多個收集器線程外,其余行為包括算法震捣、STW荔棉、對象分配規(guī)則、回收策略等都與Serial收集器一模一樣蒿赢。
對應(yīng)的這種收集器是虛擬機運行在Server模式的默認新生代收集器润樱,在單CPU環(huán)境中,ParNew收集器并不會比Serial收集器有更好的效果壹若。
- Serial收集器在新生代的多線程版本
- 使用復制算法(針對新生代)
- 只有在多CPU的環(huán)境下,效率才會比Serial收集器高
- 可以通過-XX:ParallelGCThreads來控制GC的線程數(shù)皂冰。需要結(jié)合具體的CPU個數(shù)
- Server模式下新生代的缺省收集器
Parallel Scavenge收集器
Parallel Scavenge 收集器也是一個多線程收集器店展,也是使用復制算法,但它的對象分配規(guī)則與回收策略都與ParNew收集器有所不同秃流,它是以吞吐量最大化(即GC時間占總運行時間最新冈獭)為目標的收集器實現(xiàn),它允許較長時間的STW換區(qū)總吞吐量最大化剔应。
Serial Old收集器
Serial Old是單線程收集器睡腿,使用標記-整理算法,是老年代回收器峻贮。
Parallel Old收集器
老年代版本吞吐量優(yōu)先收集器席怪,使用多線程和標記-整理算法,JVM1.6提供纤控,在此之前挂捻,新生代使用了PS收集器算法的話,老年代除Serial Old外別無選擇船万,應(yīng)為PS無法與CMS收集器配合工作刻撒。
- Parallel Scavenge在老年代實現(xiàn)
- 采用多線程,Mark-Compact算法
- 更注重吞吐量
- PS+PO = 高吞出量耿导,但GC停頓可能不理想声怔。
CMS收集器
CMS是一種以最短停頓時間為目標的收集器,使用CMS并不能達到GC效率效率最高(總體GC時間最胁丈搿)醋火,但它能盡可能降低GC時服務(wù)的停頓時間悠汽,CMS收集器使用的標記-清除算法。
- 追求最短停頓時間芥驳,非常適合Web引用
- 只針對老年區(qū)柿冲,一般結(jié)合ParNew使用
- Concurrent, GC線程和用戶線程并發(fā)工作(盡量并發(fā))兆旬。
- 使用Mark-Sweep算法
- 只有在多CPU環(huán)境下才有意義
- 使用-XX:+UseConcMarkSweepGC打開
CMS收集器缺點: - CMS以犧牲CPU資源的代價來減少用戶線程的停頓假抄。當CPU個數(shù)少于4的時候,有可能對吞吐量影響非常大丽猬。
- CMS在并發(fā)清理的過程中宿饱,用戶線程還在跑。這時候需要預(yù)留一部分空間給用戶線程脚祟。
- CMS用Mark-Sweep刑棵,會帶來碎片問題。碎片過多的時候容易頻繁觸發(fā)Full GC愚铡。
CMS是基于”標記-清除“算法實現(xiàn)的,這個過程分為4個步驟:
- 初始標記(CMS initial mark)
初始標記只是標記一下GC Roots能直接關(guān)聯(lián)的對象胡陪,速度很快沥寥。 - 并發(fā)標記(CMS concurrent mark)
就是GC Roots Tracing的過程 - 重新標記(CMS remark)
為了修正并發(fā)標記標記期間因用戶程序繼續(xù)運作而導致標記產(chǎn)生變動的那一部分對象的標記記錄,這個階段的停頓時間一般會比初始標記階段稍長一些柠座,當遠比并發(fā)標記時間短邑雅。 - 并發(fā)清除(CMS concurrent sweep)
其中,初始標記和重新標記這兩個步驟仍然需要 STW
CMS收集器在整個過程中耗時最長的的并發(fā)標記和并發(fā)清除過程收集器線程都可以與用戶線程一起工作妈经。因此淮野,從總體上看,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的吹泡。
詳細過程解析
-
Initial Mark
這是CMS兩次stop-the-world事件的其中一次骤星,這個階段的目標是:標記那些直接被GC root引用或者被年輕代存活對象所引用的所有對象。
image.png -
Concurrent Mark
在這個階段Garbage Collector會遍歷老年代爆哑,然后標記所有存活對象洞难,它會根據(jù)上個階段找到的GC Roots遍歷查找。并發(fā)標記階段揭朝,它會與用戶的應(yīng)用程序并發(fā)運行队贱,并不是老年代所有的存活對象都會標記,因為在并發(fā)期間用戶的程序可能會改變一些引用潭袱。
image.png
在上面的圖中柱嫌,與階段1的圖進行對比,就會發(fā)現(xiàn)有一個對象的引用已經(jīng)發(fā)生了變化屯换。
-
Concurrent Preclean
這是一個并發(fā)階段编丘,與應(yīng)用線程并發(fā)運行,并不會stop應(yīng)用的線程。在并發(fā)運行的過程中瘪吏,一些對象的引用可能會發(fā)生變變化癣防,但是這種情況發(fā)生時,JVM會將這個對象的區(qū)域(Card)標記為Dirty掌眠,這也就是Card Marking
image.png
在pre-clean階段蕾盯,那些能夠從Dirty對象到達的對象也會被標記,這個標記做完之后蓝丙,dirty card標記就會被清除了级遭。
image.png - Concurrent Abortable Preclean
這也是一個并發(fā)階段,但是同樣不會影響用戶的應(yīng)用線程渺尘,這個階段是為了盡量承擔STW中最終的標記階段的工作挫鸽。這個階段持續(xù)時間依賴于很多的因素,由于這個階段是在重復做很多相同的工作鸥跟,直接滿足一些條件(比如:重復迭代的次數(shù)丢郊、完成的工作量或者時鐘時間等) - Final Remark
這個是第二個STW階段,也是CMS中的最后一個医咨,這個階段的目標是標記老年代所有的存活對象枫匾,由于之前的階段是并發(fā)執(zhí)行的,gc線程可能跟不上應(yīng)用程序的變化拟淮,為了完成標記老年代所有存活對象的目標干茉,STW就非常有必要了。
通常CMS的Final Remark階段會在年輕代盡可能干凈的時候運行很泊,目的是為了減少連續(xù)STW發(fā)生的可能行(年輕代存活對象過多的話角虫,也會導致老年代涉及的存活對象會很多)。這個階段會比前面的幾個階段更復雜一些委造。
經(jīng)過以上五個階段之后戳鹅,老年代所有存活的對象都被標記過了,現(xiàn)在可以通過清除算法去清理那些老年代不再使用的對象昏兆。 -
Concurrent Sweep
這里不需要STW粉楚,它是與用戶的應(yīng)用程序并發(fā)運行,清除那些不再使用的對象回收它們的占用空間為將來使用亮垫。
image.png - Concurrent Reset
這個階段也是并發(fā)執(zhí)行的模软,它會重置CMS內(nèi)部數(shù)據(jù)結(jié)構(gòu),為下次的GC做準備饮潦。
總結(jié)
CMS通過將大量工作分散到并發(fā)處理階段來減少STW時間燃异,在這塊做得非常優(yōu)秀,但是CMS也有一些其他的問題继蜡。
CMS收集器無法處理浮動垃圾(Floating Garbage)回俐,可能出現(xiàn)“Concurrent Mode Failure”失敗而導致另一次Full GC的產(chǎn)生逛腿,可能引發(fā)串行Full GC。
空間碎片仅颇,導致無法分配大對象单默,CMS收集器提供了一個-XX:+UseCMSCompactAtFullCollection開關(guān)參數(shù)(默認開啟),用于在CMS收集器頂不住要進行Full GC時開啟內(nèi)存碎片的合并整理過程忘瓦,內(nèi)存整理的過程是無法并發(fā)的搁廓,空間碎片問題沒有了,但停頓時間不得不變長耕皮。
對于堆較大的引用境蜕,GC的時間難以預(yù)估。
G1收集器
- G1收集器是一個面向服務(wù)端的垃圾收集器凌停,適用于多核處理器粱年、大內(nèi)存容量的服務(wù)端系統(tǒng)。
- 它滿足短時間gc停頓的同時達到一個較高的吞吐量罚拟。
- JDK7以上版本適用
設(shè)計目標
- 與應(yīng)用線程同時工作台诗,幾乎吧需要stop the world(與CMS類似)
- 整理剩余空間,不產(chǎn)生內(nèi)存碎片(CMS只能在Full GC時赐俗,用stop the world整理內(nèi)存碎片)
- GC停頓更加可控
- 不犧牲系統(tǒng)的吞吐量
- gc不要求額外的內(nèi)存空間(CMS需要預(yù)留空間存儲浮動垃圾)
G1設(shè)計規(guī)劃 要替換掉CMS
G1在某些方面彌補了CMS的不足拉庶, 比如CMS算法使用的是mark-sweep算法,自然會產(chǎn)生內(nèi)存碎片秃励;然而G1基于copying算法,高效的整理剩余內(nèi)存吉捶,而不需要管理內(nèi)存碎片夺鲜。
另外,G1提供了更多手段呐舔,以達到對gct停頓時間的可控币励。
G1收集器堆的結(jié)構(gòu)
- head被劃分為一個個相等的不連續(xù)的內(nèi)存區(qū)域(region),每個region都有一個分代角色:eden珊拼、survivor食呻、old
- 對每個角色的數(shù)量并沒有強制限定,也就是說對每種分代的內(nèi)存大小澎现,可以動態(tài)變化仅胞。
- G1最大的特點就是高效地執(zhí)行回收,優(yōu)先去執(zhí)行那些大量對象的可回收區(qū)域剑辫。
- G1使用了gc停頓可預(yù)測模型干旧,來滿足用戶設(shè)定gc停頓時間,根據(jù)用戶設(shè)定的目標時間妹蔽,G1會自動的選擇哪些region要清除椎眯,一次清除多少個region
- G1從多個region中復制存活對象,然后集中放到一個region中挠将,同時整理、清理內(nèi)存(copying收集算法)
G1的重要概念
- 分區(qū)
G1采用了不同的策略來解決并行编整、串行和CMS收集器的碎片舔稀、暫停時間不可控等問題--G1將整個堆分成相同大小的分區(qū)。每個分區(qū)即可能是年輕代也可能是老年代掌测,但在某一時刻只能屬于某個代内贮,分代概念還在,成為邏輯上的概念赏半,這樣方便復用之前分代框架的邏輯贺归。-
在物理上不需要連續(xù),則帶來了額外的好處--有的分區(qū)垃圾對象很少断箫,有的分區(qū)垃圾對象特別多,G1會優(yōu)先回收垃圾特別多的分區(qū)
拂酣,這樣可以花費較少的時間來回收這些分區(qū)的垃圾,也就是G1名字的由來仲义,即首先收集垃圾最多的分區(qū)婶熬。
依然是在新生代滿的時候,對整個新生代進行回收--整個新生代的對象要么被回收埃撵、要么被晉升赵颅,至于新生代也采取分區(qū)機制的原因,則是因為這樣更老年代的策略統(tǒng)一暂刘,方便調(diào)整代的大小饺谬。 - 收集集合(CSet)
一組可被回收的分區(qū)的集合。在CSet中存活的數(shù)據(jù)會在GC過程中被移動到另一個可用分區(qū)谣拣,CSet中的分區(qū)可以來自eden分區(qū)募寨、survivor分區(qū)或者老年代。 -
已記憶集合(RSet)
RSet記錄了其他Region對象引用本Region中對象的關(guān)系森缠,屬于points-into結(jié)構(gòu)(誰引用了我了的對象)拔鹰。RSet的價值在于使得垃圾收集器不需要掃描整個堆找到誰引用了當前分區(qū)的對象,只掃描RSet即可贵涵。
G1 GC是在points-out的card table之上再加了一層結(jié)構(gòu)來構(gòu)成points-into RSet:每個regionh會記錄下到底哪些別的region有指向自己的指針列肢,而這些指針分別在哪些card的范圍內(nèi)。
這個RSet其實是一個hash table宾茂,key是別的region的起始位置瓷马,value是一個集合,里邊的元素是card table的index跨晴。舉個例子來說决采,如果region A的RSet里有一項的key是region B,value里有index為1234的card坟奥,他的意思就是region B的一個Card里有引用指向region A树瞭。所以對A來說該RSet記錄的是points-into的關(guān)系拇厢,而card table仍然記錄points-out的關(guān)系。
- Snapshot-At-The-Beginning(SATB)
SATB是G1 GC在并發(fā)標記階段使用的增量式的標記算法晒喷。
并發(fā)標記是多線程的孝偎,但并發(fā)線程在同一時刻只掃描一個分區(qū)。
G1相對于CMS的優(yōu)勢
- G1在壓縮空間方面有優(yōu)勢
- G通過將內(nèi)存空間分成區(qū)域(Region)的方式避免內(nèi)存碎片問題
- Eden凉敲、Survivor衣盾、Old區(qū)不再固定,在內(nèi)存使用效率上來說更靈活
- G1可以通過設(shè)置預(yù)期停頓時間(Pause TIme)來控制垃圾收集時間爷抓,避免應(yīng)用雪崩現(xiàn)象
- G1在回收內(nèi)存后會馬上同時做合并空閑內(nèi)存的工作势决,而CMS默認是在STW的時候做
- G1會在Young GC中使用,而CMS只能在Old區(qū)使用
G1的適合場景
- 服務(wù)端多核CPU蓝撇、JVM內(nèi)存占用較大的引用
- 應(yīng)用在運行過程中會產(chǎn)生大量內(nèi)存碎片果复、需要經(jīng)常壓縮空間
- 向要更可控、可預(yù)期的GC停頓周期渤昌;防止高并發(fā)下應(yīng)用的雪崩現(xiàn)象
G1 GC模式
G1提供了兩種GC模式虽抄,Young GC和Mixed GC,兩種都是完全STW的独柑。
- Young GC:選定所有年輕代里的Region迈窟。通過控制年輕代的Region個數(shù),即年輕代內(nèi)存大小來控制Young GC的時間開銷忌栅。
- Mixed GC:選定所有年輕代里的Region车酣,外加根據(jù)global concurrent marking統(tǒng)計得出收集收益高的若干老年代Region。在用戶指定的開銷目標范圍內(nèi)盡可能選擇收益高的老年代Region
Mixed GC不是Full GC索绪,它只能回收部分老年代的region湖员, 如果Mixed GC實在無法跟上程序分配內(nèi)存的速度,導致老年代填滿無法繼續(xù)進行Mixed GC者春, 就會使用serial old GC(Full GC)來收集整個GC heap。所以本質(zhì)上清女,G1是不提供Full GC的钱烟。
Global Concurrent Marking
Global Concurrent Marking的執(zhí)行過程類似于CMS,但是不同的是嫡丙,在G1 GC中拴袭,它主要是為Mixed GC提供標記服務(wù)的,并不是一次GC過程的一個必須環(huán)節(jié)曙博。
Global Concurrent Marking的執(zhí)行過程分為四個步驟:
- 初始標記(initial mark拥刻,STW):它標記了從GC Root開始直接可達的對象。
共用了Young GC的的暫停父泳,這是因為它們可以服用root scan操作般哼,所有可以說global concurrent marking是伴隨Young GC而發(fā)生的吴汪。 - 并發(fā)標記(Concurrent Marking):這個階段從GC Root開始對heap中的對象進行標記,標記線程與應(yīng)用程序線程并發(fā)執(zhí)行蒸眠,并且收集各個Region的存活對象信息漾橙。
- 重新標記(Remark, STW):標記那些在并發(fā)標記階段發(fā)生變化的對象楞卡,將被回收霜运。
- 清理(Cleanup):清除空Region(沒有存活對象的),加入到free list蒋腮。
G1在運行過程中的主要模式
YGC(不同于CMS)
G1 YGC在Eden充滿時觸發(fā)淘捡,在回收之后所有之前屬于Eden的區(qū)塊全部變成空白,即不屬于任何一個分區(qū)池摧。并發(fā)階段
-
混合模式
什么時候會發(fā)生Mixed GC
由一些參數(shù)控制焦除,另外也控制著哪些老年代Region會被選人CSet(收集集合)。
G1HeapWastePercent:在global concurrent marking結(jié)束之后险绘,我們可以知道old gen regions中有多少空間要被回收踢京,在每次YGC之后和再次發(fā)生Mixed GC之前,會檢查垃圾占比是否達到此參數(shù)宦棺,只有達到了瓣距,下次才會發(fā)生Mixed GC。
G1MixedGCLiveThresholdPercent:old generation region中的存活對象的占比代咸,只有在此參數(shù)之下蹈丸,才會被選入CSet
G1MixedGCCountTarget:一次global concurrent marking之后,最多執(zhí)行Mixed GC的次數(shù)
G1OldCSetRegionThresholdPercent:一次Mixed GC中能選入CSet的最多old region數(shù)量呐芥。
Full GC(一般是G1出現(xiàn)問題時發(fā)生)逻杖。
Humongous區(qū)域
在G1中,還有一種特殊的區(qū)域思瘟,叫Humongous區(qū)域荸百。如果一個對象占用的空間達到或是超過了分區(qū)容量50%以上,G1收集器就認為這是一個巨型對象滨攻。這些巨型對象够话,默認直接會被分配在老年代,但是如果它是一個短期存在的巨型對象光绕,就會對垃圾收集器造成負面影響女嘲。為了解決這個問題,G1劃分了一個Humougous區(qū)诞帐,它用來專門存放巨型對象欣尼。如果一個H區(qū)裝不下一個巨型對象,那么G1會尋找連續(xù)的H分區(qū)來存儲停蕉。為了能找到連續(xù)的H區(qū)愕鼓,有時候不得不啟動Full GC钙态。
G1 Young GC
Young GC主要是對Eden區(qū)進行GC,它在Eden空間耗盡時會被觸發(fā)拒啰。在這種情況下驯绎,Eden空間的數(shù)據(jù)移動到Survivor空間中,如果Survivor空間不夠谋旦,Eden空間的部分數(shù)據(jù)會直接晉升到老年代空間剩失。Survivor區(qū)的數(shù)據(jù)移動到新的Survivor區(qū)中,也有部分數(shù)據(jù)晉升到老年代空間中册着。最終Eden空間的數(shù)據(jù)為空拴孤,GC完成工作,引用線程繼續(xù)執(zhí)行甲捏。
如果僅僅GC新生代對象演熟,我們?nèi)绾握业剿械母鶎ο竽兀坷夏甏乃袑ο蠖际歉鶈崴径伲磕敲催@樣掃描下來會耗費大量的時間芒粹。于是,G1引進了RSet的概念大溜。它的全稱是Remembered Set化漆,作用是跟蹤指向某個heap區(qū)內(nèi)的對象引用。
由于新生代有多個钦奋,那么我們需要在新生代之間記錄引用嗎座云,這是不必要的,原因在于每次GC時付材,所有新生代都會被掃描朦拖,所以只需要記錄老年代到新生代之間的引用即可。
- 根掃描
靜態(tài)和本地對象被掃描 - 更新RS
處理dirty card隊列更新RS - 處理RS
檢測從年輕代指向老年代的對象 - 對象拷貝
拷貝存活的對象到Survivor/old區(qū)域 - 處理引用隊列
軟引用厌衔,弱引用璧帝,虛引用處理
三色標記算法
提到并發(fā)標記,我們不得不了解并發(fā)標記的三色標記算法富寿。它是描述追蹤式回收器的一種有效的方法睬隶,利用它可以推演回收器的正確性。
- 黑色:根對象作喘,或者該對象與它的子對象都被掃描過的
- 灰色:對象本身被掃描理疙,但還沒掃描完該對象的子對象
- 白色:未被掃描對象晕城,掃描完成所有對象之后泞坦,最終為白色的為不可達對象,即垃圾對象砖顷。
當 GC 開始掃描對象時贰锁,按照如下圖步驟進行對象的掃描:
根對象被置為黑色赃梧,子對象被置為灰色:
繼續(xù)由灰色遍歷,將已掃描了子對象的對象置為黑色:
遍歷了所有可達的對象后,所有可達的對象都變成了黑色豌熄;不可達的對象即為白色授嘀,需要被清理:
但是如果在標記過程中,應(yīng)用程序也在運行锣险,那么對象的指針就有可能改變蹄皱。這樣的話,我們就會遇到一個問題:對象丟失問題芯肤。
我們看下面一種情況巷折,當垃圾收集器掃描到下面情況時:
這時候應(yīng)用程序執(zhí)行了以下操作:
A.c = C
B.c = null
這樣,對象的狀態(tài)圖變成如下情形:
這時候垃圾收集器再標記掃描的時候就會成下圖這樣:
很顯然崖咨,此時 C 是白色的锻拘,被認為是垃圾需要清理掉,顯然這是不合理的击蹲。那么我們?nèi)绾伪WC應(yīng)用程序在運行的時候署拟,GC 標記的對象不丟失呢?有如下兩種可行的方式:
- 在插入的時候記錄對象
- 在刪除的時候記錄對象
在 G1 中歌豺,使用的是 SATB(Snapshot-At-The-Beginning)的方式推穷,刪除的時候記錄所有的對象,它有3個步驟:
- Step-1:在初始標記的時候世曾,生成一個快照圖缨恒,用于標記存活對象。
- Step-2:在并發(fā)標記的時候轮听,所有被改變的對象入隊(在 Write Barrier 里把所有舊的引用所指向的對象都變成非白的)骗露。
- Step-3:可能存在游離的垃圾,將在下次被收集血巍。
這樣萧锉,G1 到現(xiàn)在可以知道哪些老的分區(qū)可回收的垃圾最多。當全局并發(fā)標記完成后述寡,在某個時刻柿隙,就開始了 Mix GC。這些垃圾回收被稱作“混合式”是因為他們不僅僅進行正常的新生代垃圾收集鲫凶,同時也回收部分后臺掃描線程標記的分區(qū)禀崖。混合式垃圾收集如下圖:
混合式 GC 也是采用的復制的清理策略螟炫,當 GC 完成后波附,會重新釋放空間。