JVM學(xué)習(xí)-垃圾回收器

Serial收集器

單線程收集器,收集時會暫停所有工作線程(Stop The World),虛擬機運行在Client模式時的默認(rèn)新生代收集器。

  1. 最早的收集器,單線程進(jìn)行GC
  2. New和Old Generation 都可以使用
  3. 在新生代,采用復(fù)制算法那;在老年代,采用Mark-Compact算法
  4. 因為是單線程GC, 沒有多線程切換的額外開銷,簡單使用
  5. Hotspot Client模式缺省的收集器


    image.png

ParNew收集器

ParNew收集器就是Serial的多線程版本,除了多個收集器線程外,其余行為包括算法、STW、對象分配規(guī)則、回收策略等都與Serial收集器一模一樣。
對應(yīng)的這種收集器是虛擬機運行在Server模式的默認(rèn)新生代收集器,在單CPU環(huán)境中,ParNew收集器并不會比Serial收集器有更好的效果。

  1. Serial收集器在新生代的多線程版本
  2. 使用復(fù)制算法(針對新生代)
  3. 只有在多CPU的環(huán)境下,效率才會比Serial收集器高
  4. 可以通過-XX:ParallelGCThreads來控制GC的線程數(shù)。需要結(jié)合具體的CPU個數(shù)
  5. Server模式下新生代的缺省收集器

Parallel Scavenge收集器

Parallel Scavenge 收集器也是一個多線程收集器,也是使用復(fù)制算法,但它的對象分配規(guī)則與回收策略都與ParNew收集器有所不同,它是以吞吐量最大化(即GC時間占總運行時間最?。槟繕?biāo)的收集器實現(xiàn),它允許較長時間的STW換區(qū)總吞吐量最大化。

Serial Old收集器

Serial Old是單線程收集器,使用標(biāo)記-整理算法,是老年代回收器。

Parallel Old收集器

老年代版本吞吐量優(yōu)先收集器,使用多線程和標(biāo)記-整理算法,JVM1.6提供,在此之前,新生代使用了PS收集器算法的話,老年代除Serial Old外別無選擇,應(yīng)為PS無法與CMS收集器配合工作。

  1. Parallel Scavenge在老年代實現(xiàn)
  2. 采用多線程,Mark-Compact算法
  3. 更注重吞吐量
  4. PS+PO = 高吞出量,但GC停頓可能不理想。

CMS收集器

CMS是一種以最短停頓時間為目標(biāo)的收集器,使用CMS并不能達(dá)到GC效率效率最高(總體GC時間最?。?,但它能盡可能降低GC時服務(wù)的停頓時間,CMS收集器使用的標(biāo)記-清除算法。

  1. 追求最短停頓時間,非常適合Web引用
  2. 只針對老年區(qū),一般結(jié)合ParNew使用
  3. Concurrent, GC線程和用戶線程并發(fā)工作(盡量并發(fā))。
  4. 使用Mark-Sweep算法
  5. 只有在多CPU環(huán)境下才有意義
  6. 使用-XX:+UseConcMarkSweepGC打開
    CMS收集器缺點:
  7. CMS以犧牲CPU資源的代價來減少用戶線程的停頓。當(dāng)CPU個數(shù)少于4的時候,有可能對吞吐量影響非常大。
  8. CMS在并發(fā)清理的過程中,用戶線程還在跑。這時候需要預(yù)留一部分空間給用戶線程。
  9. CMS用Mark-Sweep,會帶來碎片問題。碎片過多的時候容易頻繁觸發(fā)Full GC。
    CMS是基于”標(biāo)記-清除“算法實現(xiàn)的,這個過程分為4個步驟:
  • 初始標(biāo)記(CMS initial mark)
    初始標(biāo)記只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)的對象,速度很快。
  • 并發(fā)標(biāo)記(CMS concurrent mark)
    就是GC Roots Tracing的過程
  • 重新標(biāo)記(CMS remark)
    為了修正并發(fā)標(biāo)記標(biāo)記期間因用戶程序繼續(xù)運作而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄,這個階段的停頓時間一般會比初始標(biāo)記階段稍長一些,當(dāng)遠(yuǎn)比并發(fā)標(biāo)記時間短。
  • 并發(fā)清除(CMS concurrent sweep)
    其中,初始標(biāo)記和重新標(biāo)記這兩個步驟仍然需要 STW
    CMS收集器在整個過程中耗時最長的的并發(fā)標(biāo)記和并發(fā)清除過程收集器線程都可以與用戶線程一起工作。因此,從總體上看,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的。

詳細(xì)過程解析

  1. Initial Mark
    這是CMS兩次stop-the-world事件的其中一次,這個階段的目標(biāo)是:標(biāo)記那些直接被GC root引用或者被年輕代存活對象所引用的所有對象。


    image.png
  2. Concurrent Mark
    在這個階段Garbage Collector會遍歷老年代,然后標(biāo)記所有存活對象,它會根據(jù)上個階段找到的GC Roots遍歷查找。并發(fā)標(biāo)記階段,它會與用戶的應(yīng)用程序并發(fā)運行,并不是老年代所有的存活對象都會標(biāo)記,因為在并發(fā)期間用戶的程序可能會改變一些引用。


    image.png

    在上面的圖中,與階段1的圖進(jìn)行對比,就會發(fā)現(xiàn)有一個對象的引用已經(jīng)發(fā)生了變化。

  3. Concurrent Preclean
    這是一個并發(fā)階段,與應(yīng)用線程并發(fā)運行,并不會stop應(yīng)用的線程。在并發(fā)運行的過程中,一些對象的引用可能會發(fā)生變變化,但是這種情況發(fā)生時,JVM會將這個對象的區(qū)域(Card)標(biāo)記為Dirty,這也就是Card Marking


    image.png

    在pre-clean階段,那些能夠從Dirty對象到達(dá)的對象也會被標(biāo)記,這個標(biāo)記做完之后,dirty card標(biāo)記就會被清除了。


    image.png
  4. Concurrent Abortable Preclean
    這也是一個并發(fā)階段,但是同樣不會影響用戶的應(yīng)用線程,這個階段是為了盡量承擔(dān)STW中最終的標(biāo)記階段的工作。這個階段持續(xù)時間依賴于很多的因素,由于這個階段是在重復(fù)做很多相同的工作,直接滿足一些條件(比如:重復(fù)迭代的次數(shù)、完成的工作量或者時鐘時間等)
  5. Final Remark
    這個是第二個STW階段,也是CMS中的最后一個,這個階段的目標(biāo)是標(biāo)記老年代所有的存活對象,由于之前的階段是并發(fā)執(zhí)行的,gc線程可能跟不上應(yīng)用程序的變化,為了完成標(biāo)記老年代所有存活對象的目標(biāo),STW就非常有必要了。
    通常CMS的Final Remark階段會在年輕代盡可能干凈的時候運行,目的是為了減少連續(xù)STW發(fā)生的可能行(年輕代存活對象過多的話,也會導(dǎo)致老年代涉及的存活對象會很多)。這個階段會比前面的幾個階段更復(fù)雜一些。
    經(jīng)過以上五個階段之后,老年代所有存活的對象都被標(biāo)記過了,現(xiàn)在可以通過清除算法去清理那些老年代不再使用的對象。
  6. Concurrent Sweep
    這里不需要STW,它是與用戶的應(yīng)用程序并發(fā)運行,清除那些不再使用的對象回收它們的占用空間為將來使用。


    image.png
  7. Concurrent Reset
    這個階段也是并發(fā)執(zhí)行的,它會重置CMS內(nèi)部數(shù)據(jù)結(jié)構(gòu),為下次的GC做準(zhǔn)備。

總結(jié)

CMS通過將大量工作分散到并發(fā)處理階段來減少STW時間,在這塊做得非常優(yōu)秀,但是CMS也有一些其他的問題。
CMS收集器無法處理浮動垃圾(Floating Garbage),可能出現(xiàn)“Concurrent Mode Failure”失敗而導(dǎo)致另一次Full GC的產(chǎn)生,可能引發(fā)串行Full GC。
空間碎片,導(dǎo)致無法分配大對象,CMS收集器提供了一個-XX:+UseCMSCompactAtFullCollection開關(guān)參數(shù)(默認(rèn)開啟),用于在CMS收集器頂不住要進(jìn)行Full GC時開啟內(nèi)存碎片的合并整理過程,內(nèi)存整理的過程是無法并發(fā)的,空間碎片問題沒有了,但停頓時間不得不變長。
對于堆較大的引用,GC的時間難以預(yù)估。

G1收集器

  • G1收集器是一個面向服務(wù)端的垃圾收集器,適用于多核處理器、大內(nèi)存容量的服務(wù)端系統(tǒng)。
  • 它滿足短時間gc停頓的同時達(dá)到一個較高的吞吐量。
  • JDK7以上版本適用

設(shè)計目標(biāo)

  1. 與應(yīng)用線程同時工作,幾乎吧需要stop the world(與CMS類似)
  2. 整理剩余空間,不產(chǎn)生內(nèi)存碎片(CMS只能在Full GC時,用stop the world整理內(nèi)存碎片)
  3. GC停頓更加可控
  4. 不犧牲系統(tǒng)的吞吐量
  5. 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提供了更多手段,以達(dá)到對gct停頓時間的可控。

G1收集器堆的結(jié)構(gòu)

image.png
  • 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è)定的目標(biāo)時間,G1會自動的選擇哪些region要清除,一次清除多少個region
  • G1從多個region中復(fù)制存活對象,然后集中放到一個region中,同時整理、清理內(nèi)存(copying收集算法)

G1的重要概念

  • 分區(qū)
    G1采用了不同的策略來解決并行、串行和CMS收集器的碎片、暫停時間不可控等問題--G1將整個堆分成相同大小的分區(qū)。每個分區(qū)即可能是年輕代也可能是老年代,但在某一時刻只能屬于某個代,分代概念還在,成為邏輯上的概念,這樣方便復(fù)用之前分代框架的邏輯。-
    在物理上不需要連續(xù),則帶來了額外的好處--有的分區(qū)垃圾對象很少,有的分區(qū)垃圾對象特別多,G1會優(yōu)先回收垃圾特別多的分區(qū),這樣可以花費較少的時間來回收這些分區(qū)的垃圾,也就是G1名字的由來,即首先收集垃圾最多的分區(qū)。
    依然是在新生代滿的時候,對整個新生代進(jìn)行回收--整個新生代的對象要么被回收、要么被晉升,至于新生代也采取分區(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的價值在于使得垃圾收集器不需要掃描整個堆找到誰引用了當(dāng)前分區(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ā)標(biāo)記階段使用的增量式的標(biāo)記算法。
    并發(fā)標(biāo)記是多線程的,但并發(fā)線程在同一時刻只掃描一個分區(qū)。

G1相對于CMS的優(yōu)勢

  1. G1在壓縮空間方面有優(yōu)勢
  2. G通過將內(nèi)存空間分成區(qū)域(Region)的方式避免內(nèi)存碎片問題
  3. Eden、Survivor、Old區(qū)不再固定,在內(nèi)存使用效率上來說更靈活
  4. G1可以通過設(shè)置預(yù)期停頓時間(Pause TIme)來控制垃圾收集時間,避免應(yīng)用雪崩現(xiàn)象
  5. G1在回收內(nèi)存后會馬上同時做合并空閑內(nèi)存的工作,而CMS默認(rèn)是在STW的時候做
  6. G1會在Young GC中使用,而CMS只能在Old區(qū)使用

G1的適合場景

  1. 服務(wù)端多核CPU、JVM內(nèi)存占用較大的引用
  2. 應(yīng)用在運行過程中會產(chǎn)生大量內(nèi)存碎片、需要經(jīng)常壓縮空間
  3. 向要更可控、可預(yù)期的GC停頓周期;防止高并發(fā)下應(yīng)用的雪崩現(xiàn)象

G1 GC模式

G1提供了兩種GC模式,Young GC和Mixed GC,兩種都是完全STW的。

  1. Young GC:選定所有年輕代里的Region。通過控制年輕代的Region個數(shù),即年輕代內(nèi)存大小來控制Young GC的時間開銷。
  2. Mixed GC:選定所有年輕代里的Region,外加根據(jù)global concurrent marking統(tǒng)計得出收集收益高的若干老年代Region。在用戶指定的開銷目標(biāo)范圍內(nèi)盡可能選擇收益高的老年代Region
    Mixed GC不是Full GC,它只能回收部分老年代的region, 如果Mixed GC實在無法跟上程序分配內(nèi)存的速度,導(dǎo)致老年代填滿無法繼續(xù)進(jìn)行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提供標(biāo)記服務(wù)的,并不是一次GC過程的一個必須環(huán)節(jié)。
Global Concurrent Marking的執(zhí)行過程分為四個步驟:

  1. 初始標(biāo)記(initial mark,STW):它標(biāo)記了從GC Root開始直接可達(dá)的對象。
    共用了Young GC的的暫停,這是因為它們可以服用root scan操作,所有可以說global concurrent marking是伴隨Young GC而發(fā)生的。
  2. 并發(fā)標(biāo)記(Concurrent Marking):這個階段從GC Root開始對heap中的對象進(jìn)行標(biāo)記,標(biāo)記線程與應(yīng)用程序線程并發(fā)執(zhí)行,并且收集各個Region的存活對象信息。
  3. 重新標(biāo)記(Remark, STW):標(biāo)記那些在并發(fā)標(biāo)記階段發(fā)生變化的對象,將被回收。
  4. 清理(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之前,會檢查垃圾占比是否達(dá)到此參數(shù),只有達(dá)到了,下次才會發(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ū)域。如果一個對象占用的空間達(dá)到或是超過了分區(qū)容量50%以上,G1收集器就認(rèn)為這是一個巨型對象。這些巨型對象,默認(rèn)直接會被分配在老年代,但是如果它是一個短期存在的巨型對象,就會對垃圾收集器造成負(fù)面影響。為了解決這個問題,G1劃分了一個Humougous區(qū),它用來專門存放巨型對象。如果一個H區(qū)裝不下一個巨型對象,那么G1會尋找連續(xù)的H分區(qū)來存儲。為了能找到連續(xù)的H區(qū),有時候不得不啟動Full GC。

G1 Young GC

Young GC主要是對Eden區(qū)進(jìn)行GC,它在Eden空間耗盡時會被觸發(fā)。在這種情況下,Eden空間的數(shù)據(jù)移動到Survivor空間中,如果Survivor空間不夠,Eden空間的部分?jǐn)?shù)據(jù)會直接晉升到老年代空間。Survivor區(qū)的數(shù)據(jù)移動到新的Survivor區(qū)中,也有部分?jǐn)?shù)據(jù)晉升到老年代空間中。最終Eden空間的數(shù)據(jù)為空,GC完成工作,引用線程繼續(xù)執(zhí)行。
如果僅僅GC新生代對象,我們?nèi)绾握业剿械母鶎ο竽??老年代的所有對象都是根嗎?那么這樣掃描下來會耗費大量的時間。于是,G1引進(jìn)了RSet的概念。它的全稱是Remembered Set,作用是跟蹤指向某個heap區(qū)內(nèi)的對象引用。


image.png

由于新生代有多個,那么我們需要在新生代之間記錄引用嗎,這是不必要的,原因在于每次GC時,所有新生代都會被掃描,所以只需要記錄老年代到新生代之間的引用即可。

  1. 根掃描
    靜態(tài)和本地對象被掃描
  2. 更新RS
    處理dirty card隊列更新RS
  3. 處理RS
    檢測從年輕代指向老年代的對象
  4. 對象拷貝
    拷貝存活的對象到Survivor/old區(qū)域
  5. 處理引用隊列
    軟引用,弱引用,虛引用處理

三色標(biāo)記算法

提到并發(fā)標(biāo)記,我們不得不了解并發(fā)標(biāo)記的三色標(biāo)記算法。它是描述追蹤式回收器的一種有效的方法,利用它可以推演回收器的正確性。

  • 黑色:根對象,或者該對象與它的子對象都被掃描過的
  • 灰色:對象本身被掃描,但還沒掃描完該對象的子對象
  • 白色:未被掃描對象,掃描完成所有對象之后,最終為白色的為不可達(dá)對象,即垃圾對象。

當(dāng) GC 開始掃描對象時,按照如下圖步驟進(jìn)行對象的掃描:
根對象被置為黑色,子對象被置為灰色:


image.png

繼續(xù)由灰色遍歷,將已掃描了子對象的對象置為黑色:


image.png

遍歷了所有可達(dá)的對象后,所有可達(dá)的對象都變成了黑色;不可達(dá)的對象即為白色,需要被清理:
image.png

但是如果在標(biāo)記過程中,應(yīng)用程序也在運行,那么對象的指針就有可能改變。這樣的話,我們就會遇到一個問題:對象丟失問題。

我們看下面一種情況,當(dāng)垃圾收集器掃描到下面情況時:


image.png

這時候應(yīng)用程序執(zhí)行了以下操作:

A.c = C
B.c = null

這樣,對象的狀態(tài)圖變成如下情形:

這時候垃圾收集器再標(biāo)記掃描的時候就會成下圖這樣:

很顯然,此時 C 是白色的,被認(rèn)為是垃圾需要清理掉,顯然這是不合理的。那么我們?nèi)绾伪WC應(yīng)用程序在運行的時候,GC 標(biāo)記的對象不丟失呢?有如下兩種可行的方式:

  • 在插入的時候記錄對象
  • 在刪除的時候記錄對象

在 G1 中,使用的是 SATB(Snapshot-At-The-Beginning)的方式,刪除的時候記錄所有的對象,它有3個步驟:

  • Step-1:在初始標(biāo)記的時候,生成一個快照圖,用于標(biāo)記存活對象。
  • Step-2:在并發(fā)標(biāo)記的時候,所有被改變的對象入隊(在 Write Barrier 里把所有舊的引用所指向的對象都變成非白的)。
  • Step-3:可能存在游離的垃圾,將在下次被收集。

這樣,G1 到現(xiàn)在可以知道哪些老的分區(qū)可回收的垃圾最多。當(dāng)全局并發(fā)標(biāo)記完成后,在某個時刻,就開始了 Mix GC。這些垃圾回收被稱作“混合式”是因為他們不僅僅進(jìn)行正常的新生代垃圾收集,同時也回收部分后臺掃描線程標(biāo)記的分區(qū)?;旌鲜嚼占缦聢D:

混合式 GC 也是采用的復(fù)制的清理策略,當(dāng) GC 完成后,會重新釋放空間。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容