JVM_28_垃圾收集器

垃圾回收器分類和性能指標(biāo)

分類

按線程數(shù)分,可以分為串行垃圾回收器和并行垃圾回收器。


image.png
  • 串行回收器指的是在同一時(shí)間段內(nèi)只允許有一個(gè)CPU用于執(zhí)行垃圾回收操作,此時(shí)工作線程被暫停,直至垃圾收集工作結(jié)束。
    • 在諸如單CPU處理器或者較小的應(yīng)用內(nèi)存等硬件平臺不是特別優(yōu)越的場合,串行回收器的性能表現(xiàn)可以超過并行回收器和并發(fā)回收器。所以,串行回收默認(rèn)被應(yīng)用在客戶端的Cilent模式下的JVM中
    • 在并發(fā)能力比較強(qiáng)的CPU上,并行回收器產(chǎn)生的停頓時(shí)間要短于串行回收器。
  • 和串行回收相反,并行收集可運(yùn)用多個(gè)CPU同時(shí)執(zhí)行垃圾回收,因此提升了應(yīng)用的吞吐量,不過并行回收仍然與串行回收一樣,采用獨(dú)占式,使用了“Stop-the-world”機(jī)制。


按照工作模式分,可以分為并發(fā)式垃圾回收器和獨(dú)占式垃圾回收器。

  • 并發(fā)垃圾回收器與應(yīng)用線程程序交替工作,以盡可能減少應(yīng)用程序的停頓時(shí)間。
  • 獨(dú)占式垃圾回收器一旦運(yùn)行,就停止應(yīng)用程序中的所有用戶線程,直到垃圾回收過程完全結(jié)束。


    image.png



按碎片處理方式,可分為壓縮式垃圾回收器和非壓縮式垃圾回收器。

  • 壓縮式垃圾回收器會在回收完成后,對存活對象進(jìn)行壓縮整理,消除回收后的碎片。(再分配對象空間使用:指針碰撞)
  • 非壓縮式的垃圾回收器不進(jìn)行這步操作。(再分配對象空間使用:空閑列表)



按工作的內(nèi)存區(qū)間分,又可分為年輕代垃圾回收器和老年代垃圾回收器。

性能指標(biāo)
  • 吞吐量:運(yùn)行用戶代碼的時(shí)間占總運(yùn)行時(shí)間的比例。(吞吐量=運(yùn)行用戶代碼時(shí)間 /(運(yùn)行用戶代碼時(shí)間+垃圾收集時(shí)間))
  • 垃圾收集開銷:吞吐量的補(bǔ)數(shù),垃圾收集所用時(shí)間與總運(yùn)行時(shí)間的比例。
  • 暫停時(shí)間:執(zhí)行垃圾收集時(shí),程序的工作線程被暫停的時(shí)間。
  • 收集頻率:相對于應(yīng)用程序的執(zhí)行,收集操作發(fā)生的頻率。
  • 內(nèi)存占用:Java堆所占的內(nèi)存大小。
  • 快速:一個(gè)對象從誕生到被回收所經(jīng)歷的時(shí)間。

在設(shè)計(jì)GC算法時(shí),一個(gè)GC算法只可能針對兩個(gè)目標(biāo)之一(即只專注較大吞吐量或最小暫停時(shí)間),或嘗試找到一個(gè)二者的折中。
現(xiàn)在標(biāo)準(zhǔn):在最大吞吐量優(yōu)先的情況下,降低延遲時(shí)間

垃圾回收器概述

7款經(jīng)典收集器
  • 串行回收器:Serial、Serial Old
  • 并行回收器:ParNew、Parallel Scavenge、Parallel Old
  • 并發(fā)回收器:CMS、G1
7款經(jīng)典收集器與垃圾分代之間的關(guān)系
image.png
垃圾收集器的組合關(guān)系

為什么會有很多收集器?因?yàn)镴ava的使用場景很多,移動端,服務(wù)端等。所以就需要針對不同的場景,提供不同的垃圾收集器,提供垃圾收集的性能。
雖然會對各個(gè)收集器進(jìn)行比較,但并非是為了挑選一個(gè)最好的收集器。沒有一種放之四海而皆準(zhǔn)。任何場景下都適用的完美收集器存在,更加沒有萬能的收集器。所以選擇的只是對具體應(yīng)用場景最合適的收集器。


image.png

如何查看默認(rèn)的垃圾收集器

  • -XX:+PrintCommandLineFlags:查看命令行相關(guān)參數(shù)(包含使用的垃圾收集器)
  • 使用命令行指令:jinfo -flag 相關(guān)垃圾回收器參數(shù) 進(jìn)程ID


Serial回收器:串行回收

  • Serial收集器是最基本、歷史最悠久的垃圾收集器。JDK1.3之前回收新生代唯一的選擇。
  • Serial收集器作為HotSpot中Client模式下的默認(rèn)新生代垃圾收集器。
  • Serial收集器采用復(fù)制算法、串行回收和“Stop-the-World”機(jī)制的方式執(zhí)行內(nèi)存回收。
  • 除了年輕代之外,Serial收集器改提供用于執(zhí)行老年代垃圾收集的Serial Old收集器。Serial Old收集器同樣也采用了串行回收和“Stop-the-World”機(jī)制,只不過內(nèi)存回收算法使用的是標(biāo)記-壓縮算法。
    • Serial Old是運(yùn)行在Client模式下默認(rèn)的老年代垃圾回收器。
    • Serial Old在Server模式下主要有兩個(gè)用途:①與新生代的Parallel Scavenge配合使用 ②作為來年代CMS收集器的后背垃圾收集方案



      這個(gè)收集器是一個(gè)單線程的收集器,但它的“單線程”的意義并不僅僅說明它只會使用一個(gè)CPU或一條收集線程去完成垃圾收集工作,更重要的是在它進(jìn)行垃圾收集時(shí),必須暫停其他所有的工作線程,直到它收集結(jié)束(Stop The World)。

  • 優(yōu)勢:簡單而高效(與其他收集器的單線程比),對于限定單個(gè)CPU的環(huán)境來說,Serial收集器由于沒有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程收集效率。(運(yùn)行在Client模式下的虛擬機(jī)是個(gè)不錯(cuò)的選擇)。
  • 在用戶桌面應(yīng)用場景中,可用內(nèi)存一般不大(及時(shí)MB至一兩百M(fèi)B),可以在較短時(shí)間內(nèi)完成垃圾收集(幾十mx至一百多ms),只要不頻繁發(fā)生,使用串行回收器是可以接受的。
  • 在HotSpot虛擬機(jī)中,使用-XX:+UserSerialGC 參數(shù)可以指定年底代和老年代都使用串行收集器。
    • 等價(jià)于新生代用Serial GC,且老年代用Serial Old GC

ParNew回收器:并行回收

  • 如果說Serial GC是年輕代中單線程垃圾收集器,那么ParNew收集器則是Serial收集器的多線程版本。(Par是Parallel的縮寫,New:只能處理新生代)
  • ParNew收集器除了采用并行回收的方式執(zhí)行內(nèi)存回收外,兩款垃圾收集器之間幾乎沒有任何區(qū)別。ParNew收集器收集器在年輕代同樣也是采用復(fù)制算法、STW機(jī)制。
  • ParNew 是很多 JVM運(yùn)行在Server模式下新生代的默認(rèn)垃圾收集器。
image.png
  • 對于新生代,回收次數(shù)頻繁,使用并行方式高效。
  • 對于老年代,回收次數(shù)少,使用串行方式節(jié)省資源。(CPU并行需要切換線程,串行可以省去切換線程的資源)
  • ParNew收集器和Serial收集器比較 :
    ParNew收集器運(yùn)行在多CPU的環(huán)境下,由于可以充分利用多CPU、多核心等物理硬件資源優(yōu)勢,可以更快速地完成垃圾收集,提升程序的吞吐量。但是單個(gè)CPU的環(huán)境下,ParNew收集器不比Serial收集器更高效。雖然Serial收集器是基于串行回收的,但是由于CPU不需要頻繁地做任務(wù)切換,因此可以有效避免多線程交互過程中產(chǎn)生的一些額外開學(xué)。
  • 除Serial外,目前只有ParNew GC能和CMS收集器配合工作。
  • 可以通過選項(xiàng)“-XX:+UseParaNewGC”手動指定使用ParNew收集器執(zhí)行內(nèi)存回收任務(wù)。它表示年輕代使用并行收集器,不影響老年代。
  • -XX:ParallelGCThreads限制線程數(shù)量,默認(rèn)開啟和CPU數(shù)據(jù)相同的線程數(shù)。

Parallel回收器:吞吐量優(yōu)先

  • HotSpot的年輕代中除了擁有ParNew收集器是基于并行回收的以外,Parallel Scavenge收集器同樣采用了復(fù)制算法、并行回收和“Stop-the-World”機(jī)制。
  • Parallel收集的出現(xiàn)是否多此一舉?
    • 和ParNew收集器不同,Parallel ScaVenge收集器的目標(biāo)是達(dá)到一個(gè)可控制的吞吐量(Throughput),它頁被稱為吞吐量優(yōu)先的垃圾收集器。
    • 自適應(yīng)調(diào)節(jié)策略也是Parallel Scavenge與ParNew一個(gè)重要區(qū)別。
  • 高吞吐量可以高效率地利用CPU時(shí)間,盡快完成程序的運(yùn)算任務(wù),主要適合在后臺運(yùn)算而不需要太多交互的任務(wù)。因此,常見在服務(wù)器環(huán)境中使用。例如:那些執(zhí)行批量處理,訂單處理,工資支付??茖W(xué)計(jì)算的應(yīng)用程序。
  • Parallel 收集器在JDK
    時(shí)提供了老年代垃圾收集的Parallel Old收集器,用來代替老年代的Serial Old收集器。
  • Parallel Old收集器采用了標(biāo)記-壓縮算法,但同樣也是基于并行回收和STW機(jī)制。
image.png
  • 在程序吞吐量優(yōu)先的應(yīng)用場景中,Parallel收集器和Parallel Old收集器的組合在Server模式下的內(nèi)存回收性能很不錯(cuò)。
  • 在Java8中,默認(rèn)是此垃圾收集器。
  • 參數(shù)配置:
    • -XX:+UseParallelGC 手動指定年輕代使用Parallel 并行收集器執(zhí)行內(nèi)存回收任務(wù)。
    • -XX"+UseParallelOldGC 手動指定老年代都是使用并行回收收集器。(分別適用于新生代和老年代。默認(rèn)JDK8是開啟的。這兩個(gè)參數(shù),默認(rèn)開啟一個(gè)。另一個(gè)也會被開啟)
    • ParallelGCThreads 設(shè)置年輕代并行收集器的線程數(shù)。一般的,最好與CPU數(shù)量相等,以避免過多的線程數(shù)影響垃圾收集性能。
      • 默認(rèn)情況下,當(dāng)CPU數(shù)量小于8個(gè),ParallelGCthreads的值等于CPU數(shù)量。
      • 當(dāng)CPU數(shù)量大于8個(gè),ParallelGCThreads的值等于3+[(5*PCU數(shù)量)/8]
    • -XX:MaxGCPauseMillis 設(shè)置垃圾收集器最大停頓時(shí)間(即STW的時(shí)間)。單位是毫秒。
    • 為了盡可能地把停頓時(shí)間控制在MaxGCPauseMills以內(nèi),收集器咋工作時(shí)會調(diào)整Java堆大小或者其他一些參數(shù)。
    • 對于用戶來講,通讀時(shí)間越短體驗(yàn)越好。但是在服務(wù)器端,我們注重高并發(fā),整體的吞吐量。所以服務(wù)器端適合Parallel進(jìn)行控制。
    • 該參數(shù)使用需謹(jǐn)慎
  • -XX:GCTimeRatio 垃圾收集時(shí)間占總時(shí)間的比例(=1/(N+1)).。用于衡量吞吐量的大小。
    • 取值范圍(0,100)。默認(rèn)值99,也就是垃圾回收時(shí)間不超過1%。
    • 與前一個(gè)-XX:MaxGCPauseMillis參數(shù)有一定矛盾性,暫停時(shí)間越長,Radio參數(shù)就容易參過設(shè)定的比例。
  • -XX:+UseAdaptiveSizePolicy 設(shè)置Parallel Scavenge收集器具有自適應(yīng)調(diào)節(jié)策略
    • 在這種模式下,年輕代的大小,Eden和Surivivor的比例。晉升老年代的對象年齡等參數(shù)會被自動調(diào)整,已達(dá)到在堆大小,吞吐量和停頓時(shí)間之間的平衡點(diǎn)。
    • 在手動調(diào)優(yōu)比較困難的場合,可以直接使用這種自適應(yīng)的方式,僅指定虛擬機(jī)的最大堆、目標(biāo)的吞吐量和停頓時(shí)間,讓虛擬機(jī)自己完成調(diào)優(yōu)工作。

CMS回收器

  • CMS(Concurrent-Mark-Sweep)收集器,這款收集器是HotSpot虛擬機(jī)中第一款真正意義上的并發(fā)收集器,它第一次實(shí)現(xiàn)了讓垃圾收集線程與用戶線程同時(shí)工作。
  • CMS收集器的關(guān)注點(diǎn)是盡可能縮短垃圾收集時(shí)用戶線程的停頓時(shí)間。停頓時(shí)間越短(低延遲)就越適合與用戶交互的程序,良好的響應(yīng)速度能提升用戶體驗(yàn)。(目前很大一部分Java應(yīng)用集中在互聯(lián)網(wǎng)站或者B/S系統(tǒng)的服務(wù)端上,這類應(yīng)用尤其重視服務(wù)的相應(yīng)速度,希望,希望系統(tǒng)停頓時(shí)間最短,以給用戶帶來 較好的體驗(yàn)。*CMS收集器就非常符合這類應(yīng)用的需求。)
  • CMS的垃圾收集算法采用標(biāo)記-清除算法,并且也會“Stop-the-world”
  • CMS無法與新生代的Parallel Scavenge配合工作,只能選擇ParNew或者Serial收集器中的一個(gè)。


    image.png

CMS的工作原理:
CMS整個(gè)過程比之前的收集器要復(fù)雜,整個(gè)過程分為4個(gè)主要階段,即初始標(biāo)記階段、并發(fā)標(biāo)記階段、重新標(biāo)記階段和并發(fā)清楚階段。

  • 初始標(biāo)記(Initial-Mark)階段:在這個(gè)階段中,程序中所有的工作線程都將會因?yàn)镾TW機(jī)制而出現(xiàn)短暫的暫停,這個(gè)階段的主要任務(wù)僅僅只是標(biāo)記出GC Roots能直接關(guān)聯(lián)到的對象。一旦標(biāo)記完成之后就會恢復(fù)之前被暫停的所有應(yīng)用線程。由于直接關(guān)聯(lián)對象比較小,所以這里的速度非???。
  • 并發(fā)標(biāo)記(Concurrent-Mark)階段:從GC Roots的直接關(guān)聯(lián)對象開始遍歷整個(gè)對象圖的過程,整個(gè)過程耗時(shí)較長但是不需要停頓用戶線程,可以與垃圾收集線程一起并發(fā)運(yùn)行。
  • 重新收集(Remark)階段:由于在并發(fā)標(biāo)記階段中,程序的工作線程會和垃圾收集線程同時(shí)運(yùn)行或者交叉運(yùn)行,因此為了修正并發(fā)標(biāo)記期間,因用戶程序執(zhí)行繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄,這個(gè)階段的停頓時(shí)間通常會比初始標(biāo)記階段稍長一些,但也遠(yuǎn)比并發(fā)標(biāo)記階段的時(shí)間短。
  • 并發(fā)清除(Concurrent-Sweep)階段:此階段清理刪除掉標(biāo)記階段判斷的已經(jīng)死亡的對象,釋放內(nèi)存空間。由于不需要移動存活對象,所以這個(gè)階段也是可以與用戶線程同時(shí)并發(fā)的。

盡管CMS收集器采用的并發(fā)回收(非獨(dú)占式),但是其初始化標(biāo)記和再次標(biāo)記這兩個(gè)階段中仍需要執(zhí)行STW機(jī)制暫停程序中的工作線程,不過暫停時(shí)間并不會太長,因此可以說明目前所有的垃圾收集器都做不到完全不需要STW,只是盡可能的縮短暫停時(shí)間。

由于最耗時(shí)間的并發(fā)標(biāo)記與并發(fā)清除階段都不需要暫停工作,所以整體的回收時(shí)低停頓的。

另外,由于在垃圾收集階段用戶線程沒有中斷,所以在CMS回收過程中,還應(yīng)該確保應(yīng)用程序用戶線程又足夠的內(nèi)存可用。因此,CMS收集器不能像其他收集器那樣等到老年代幾乎完全填滿了再進(jìn)行收集,而是當(dāng)堆內(nèi)存達(dá)到某一閾值時(shí),便開始進(jìn)行回收,以確保應(yīng)用程序在CMS工作過程中依然有足夠的內(nèi)存空間支持程序運(yùn)行。要是CMS運(yùn)行期間預(yù)留的內(nèi)存無法滿足程序需要,就會出現(xiàn)一次“Concurrent Mode Failure”失敗,這是虛擬機(jī)將啟動后背預(yù)案:臨時(shí)啟用Serial Old收集器來重新進(jìn)行老年代的垃圾收集,這樣停頓時(shí)間就很長了。

CMS收集器的垃圾算法是標(biāo)記-清除算法,這意味著每次執(zhí)行完內(nèi)存回收后,由于被執(zhí)行內(nèi)存回收的無用對象所占用的內(nèi)存空間極有可能是不連續(xù)的一些內(nèi)存塊,不可避免地將會產(chǎn)生一些內(nèi)存碎片。那么CMS在為新對象分配內(nèi)存空間時(shí),將無法使用指針碰撞技術(shù),而只能夠選擇空閑列表執(zhí)行內(nèi)存分配。

優(yōu)點(diǎn):

  • 并發(fā)收集
  • 低延遲

CMS弊端:

  • 會產(chǎn)生內(nèi)存碎片,導(dǎo)致并發(fā)清楚后,用戶線程可用的空間不足。在無法分配大對象的情況下,不得不提前出發(fā)Full GC。
  • CMS收集器對CPU資源非常敏感**。在并發(fā)階段,它雖然不會導(dǎo)致用戶停頓,但是會因?yàn)檎加昧艘徊糠志€程而導(dǎo)致應(yīng)用程序變慢,總吞吐量會降低。
  • CMS收集無法處理垃圾浮動。可能出現(xiàn)“Concurrent Mode Failure”失敗而導(dǎo)致另一次Full GC的產(chǎn)生。在并發(fā)標(biāo)記階段由于程序的工作線程和垃圾收集線程是同時(shí)運(yùn)行或者交叉運(yùn)行的,那么在并發(fā)標(biāo)記階段如果產(chǎn)生新的垃圾對象,CMS將無法對這些垃圾對象進(jìn)行標(biāo)記,最終會導(dǎo)致這些新產(chǎn)生的垃圾對象沒有被及時(shí)回收,從而只能在下一次GC執(zhí)行時(shí)釋放這些之前未被回收的內(nèi)存空間。

參數(shù)設(shè)置:

  • -XX:+UseCMSCompactAtFullCollection 用于指定在執(zhí)行完Full GC后對內(nèi)存空間進(jìn)行壓縮整理,以此避免內(nèi)存碎片的產(chǎn)生。不過由于內(nèi)存壓縮整理過程無法并發(fā)執(zhí)行,所帶來的問題就是停頓時(shí)間變得更長了。
  • -XX:CMSFullGCsBeforeCompaction 設(shè)置在執(zhí)行多少次Full GC后對內(nèi)存空間進(jìn)行壓縮整理。
  • -XX:ParallelCMSThreads 設(shè)置CMS的線程數(shù)量。
    • CMS默認(rèn)啟動的線程數(shù)是(ParallelGCThreads+3)/4,ParallelGCThreads是年輕代并行收集器的線程數(shù)。當(dāng)CPU資源比較緊張時(shí),受到CMS收集器線程的影響,應(yīng)用程序的性能在垃圾回收階段可能會非常糟糕。
  • -XX:+UseConcMarkSweepGC 手動指定使用CMS 收集器執(zhí)行你內(nèi)存回收任務(wù)。(開啟該參數(shù)會自動將-XX:+UseParNewGC打開。即:ParNew(Young區(qū)用)+CMS(Old區(qū)用)+Serial Old的組合)
  • -XX:CMSInitiatingOccupanyFraction 設(shè)置堆內(nèi)存使用率的閾值,一旦達(dá)到改閾值,便開始進(jìn)行回收。
    -JDK5的閾值是68%,JDK6以上是92%
    • 如果內(nèi)存增長緩慢,則可以設(shè)置一個(gè)稍大的值,大的閾值可以有效降低CMS的觸發(fā)頻率,減少老年代回收的次數(shù)可以較為明顯地改善應(yīng)用程序性能。反之,如果應(yīng)用程序內(nèi)存使用率增長很快,則應(yīng)該降低這個(gè)閾值,以避免頻繁觸發(fā)老年代串行收集器。因此通過該選項(xiàng)便可以有效降低Full GC的執(zhí)行次數(shù)。

小結(jié):
Hotspot有這么多的垃圾回收器,那么如果有人問,Serial GC、
Parallel GC、 Concurrent Mark Sweep GC這三個(gè)GC有什么不同呢?
請記住以下口令:
如果你想要最小化地使用內(nèi)存和并行開銷,請選Serial GC; .
如果你想要最大化應(yīng)用程序的吞吐量,請選Parallel GC;
如果你想要最小化GC的中斷或停頓時(shí)間,請選CMS GC。

image.png

G1(Garbage First)回收器:區(qū)域化分代式

為了適應(yīng)不斷擴(kuò)大的內(nèi)存和不斷增加的處理器數(shù)量,進(jìn)一步降低暫停時(shí)間,同時(shí)兼顧良好的吞吐量。官方給G1設(shè)定的目標(biāo)是在延遲可控的情況下獲得盡可能高的吞吐量,所以才擔(dān)當(dāng)起“全功能收集器”的重任與期望。


image.png

特點(diǎn):

  • 并行與并發(fā)
    • 并行性:G1在回收期間,可以有多個(gè)線程同時(shí)過賬,有效利用多核計(jì)算能力。此時(shí)用戶線程STW
    • 并發(fā)性:G1擁有與應(yīng)用程序交替執(zhí)行的能力,部分工作可以和應(yīng)用程序同時(shí)執(zhí)行,因此,一般來說,不會再整個(gè)回收階段發(fā)生完全阻塞應(yīng)用程序的情況
  • 分代收集
    • 從分代上看,G1依然屬于分代垃圾回收器,它會區(qū)分年輕代和老年代,年輕代依然有Eden去和Survivor區(qū)。但從堆的結(jié)構(gòu)上看,它不要求整個(gè)Eden區(qū)、年輕代或者老年代都是聯(lián)系的,也不堅(jiān)持固定大小和固定數(shù)量。
    • 將堆空間分為若干個(gè)區(qū)域(Region),這些區(qū)域中包含了邏輯上的年輕代和老年代。
    • 和之前的各類回收器不同,它同時(shí)兼顧年輕代和老年代。對比其他回收器,或者工作在年輕代,或者老年代。
  • 空間整合
    • CMS:“標(biāo)記-清除”算法。內(nèi)存碎片。若干次GC后進(jìn)行一次碎片整理
    • G1將內(nèi)存劃分為一個(gè)個(gè)的region。內(nèi)存的回收是以region作為基本單位。Region之間是復(fù)制算法,但整體上實(shí)際可看作是標(biāo)記-壓縮算法,這兩種算法都可以避免內(nèi)存碎片,這種特性有利于程序長時(shí)間運(yùn)行,分配大對象時(shí)不會因?yàn)闊o法找到連續(xù)內(nèi)存空間而提前出發(fā)下一次GC。尤其是當(dāng)Java堆非常大的時(shí)候,G1的優(yōu)勢更加明顯。
  • 可預(yù)測的停頓時(shí)間模型
    這是G1相對于CMS的另一個(gè)大優(yōu)勢,G1除了追求低停頓外,還能建立可預(yù)測的停頓時(shí)間模型,能讓使用者明確指定在一個(gè)長度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不得超過N毫秒。
    • 由于分區(qū)的原因,G1可以只選取部分區(qū)域進(jìn)行內(nèi)存回收,這樣縮小了回收的范圍,因此對于全局停頓情況的發(fā)生也能得到較好的控制。
    • G1跟蹤各個(gè)Region里面的垃圾堆積的價(jià)值大小(回收所獲得的的空間大小以及回收需要時(shí)間的經(jīng)驗(yàn)值),在后臺維護(hù)一個(gè)優(yōu)先列表,每次根據(jù)允許的手機(jī)時(shí)間,優(yōu)先回收價(jià)值最大的Region。保證了G1收集器在有限的時(shí)間內(nèi)可以獲取盡可能高的收集效率。
    • 相比于CMS GC,G1未必能做到CMS在最好情況下的延時(shí)停頓,但是最差情況要好很多。

缺點(diǎn):
相較于CMS,G1還不具備全方位、壓倒性優(yōu)勢。比如在用戶程序運(yùn)行過程中,G1無論是為了垃圾收集產(chǎn)生的內(nèi)存占用還是程序運(yùn)行時(shí)的額外執(zhí)行負(fù)載都要比CMS要高。
從從經(jīng)驗(yàn)上來說,在小內(nèi)存應(yīng)用上CMS的表現(xiàn)大概率會優(yōu)于G1,而G1在大內(nèi)存應(yīng)用上則發(fā)揮其優(yōu)勢。平衡點(diǎn)在6-8GB之間。

參數(shù)設(shè)置:


image.png

應(yīng)用場景:

image.png

分區(qū)Region:化整為零
使用G1收集器時(shí),它將整個(gè)Java堆劃分為約2048個(gè)大小相同的獨(dú)立Region塊,每個(gè)Region塊大小根據(jù)堆空間的實(shí)際大小而定,整體被控制在1MB到32MB之間,且為2的N次冪,即1MB,2MB,8MB,16MB,32MB。可以通過-XX:G1HeapRegionSize設(shè)定。所有的Region大小相同,且在JVM生命周期內(nèi)不會被改變。
雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔離了,他們都是一部分Region(不需要連續(xù))的集合。通過Region的動態(tài)分配實(shí)現(xiàn)邏輯上的連續(xù)。

image.png

  • 一個(gè)region有可能屬于Eden,Survivor或者Old/Tenured內(nèi)存區(qū)域。但是一個(gè)region只可能屬于一個(gè)角色。
  • G1垃圾收集器還增加了一個(gè)新的內(nèi)存區(qū)域,叫做Humongous內(nèi)存區(qū)域。主要用戶存儲大對象,如果超過1.5個(gè)region,就放到H。
    設(shè)置H的原因:
    對于堆中的大對象,默認(rèn)直接會被分配到老年代,但是如果它是一個(gè)短期存在的大對象,就會對垃圾收集器造成負(fù)面影響。為了解決這個(gè)問題,G1劃分了一個(gè)Humongous區(qū),它們用來專門存放大對象。如果一個(gè)H區(qū)裝不下一個(gè)大對象,那么G1會尋找到連續(xù)的H區(qū)來存儲。為了能找到連續(xù)的H區(qū),有時(shí)候不得不啟動Full GC。G1的大多數(shù)行為都把H區(qū)作為老年代的一份來看待。
  • region內(nèi)使用指針碰撞分配。

G1回收器垃圾回收過程
G1 GC的垃圾回收過程主要包括如下三個(gè)環(huán)節(jié):

  • 年輕代GC(Young GC)
  • 老年代并發(fā)標(biāo)記過程(Concurrent Marking)
  • 混合回收(Mixed GC)
  • (如果需要,單線程、獨(dú)占式、高強(qiáng)度的Full GC還是繼續(xù)存在的。它針對GC的評估失敗提供了一種失敗保護(hù)機(jī)制,即強(qiáng)力回收。)
image.png
  1. 應(yīng)用程序分配內(nèi)存,當(dāng)年輕代的Eden去用盡時(shí)開始年輕代回收過程;G1的年輕代收集階段是一個(gè)并行的獨(dú)占式收集器。在年輕代回收期,G1 GC暫停所有應(yīng)用程序線程,啟動多線程執(zhí)行年輕代回收。然后從年輕代區(qū)間移動存活對象到Survivor區(qū)間或者老年區(qū)間,也有可能是兩個(gè)區(qū)間都會涉及。
  2. 當(dāng)堆內(nèi)存使用達(dá)到一定值(默認(rèn)45%)時(shí),開始老年代并發(fā)標(biāo)記過程。
  3. 標(biāo)記完成馬上開始混合回收過程。對于一個(gè)混合回收器,G1 GC從老年區(qū)間移動存活對象到空閑區(qū)間,這些空閑區(qū)間也就成為了老年代的一部分。和年輕代不同,老年代的G1回收器和其他GC不同,G1的老年代回收器不需要整個(gè)老年代被回收,一次只需要掃描/回收一小部分老年代的Region就可以了。同時(shí),這個(gè)老年代Region是和年輕代一起回收的。

記憶集(Remembered Set)——解決分代引用問題
問題:

  • 一個(gè)對象被不同區(qū)域引用的問題
  • 一個(gè)Region是不可能孤立的,一個(gè)Region中的對象可能被其他任意Region中對象引用,判斷對象存活時(shí),是否需要掃描整個(gè)Java堆才能保證準(zhǔn)確?
  • 在其他的分代收集器,也存在這樣的問題(G1更突出)
  • 回收新生代也不得不掃描老年代?
  • 這樣子的話會降低Minor GC的效率;

解決方法:
無論G1還是其他分代收集器,JVM都是使用Remembered Set來避免全局掃描;

  • 每個(gè)Region都有一個(gè)對應(yīng)的Remembered Set;
  • 每次Reference類型數(shù)據(jù)寫操作時(shí),都會產(chǎn)生一個(gè)(寫屏障)Write Barrier暫時(shí)中斷操作;
  • 然后檢查將要寫入的引用指向的對象是否和該Reference類型數(shù)據(jù)在不同的Region(其他收集器:檢查老年代對象是否引用了新生代對象);
  • 如果不同,通過CardTable把相關(guān)引用信息記錄到引用指向?qū)ο蟮乃赗egion對應(yīng)的Remembered Set中;
  • 當(dāng)進(jìn)行垃圾收集時(shí),在GC根節(jié)點(diǎn)的枚舉范圍加入Remembered Set;就可以保證不進(jìn)行全局掃描,也不會有遺漏。
G1回收詳細(xì)過程
過程一:年輕代GC

JVM啟動時(shí),G1先準(zhǔn)備Eden區(qū),程序在運(yùn)行過程中不斷創(chuàng)建對象到Eden去,當(dāng)Eden區(qū)耗盡時(shí),G1會啟動一次年輕代垃圾回收過程。

年輕代垃圾回收只會回收Eden區(qū)和Survicor區(qū)。

YGC時(shí),首先暫停應(yīng)用程序的執(zhí)行(STW),G1創(chuàng)建回收集(Collection Set),回收集是指需要被回收的內(nèi)存分段的集合,年輕代回收過程的回收集包含年輕代Eden區(qū)和Survivor區(qū)所有的內(nèi)存分段。

image.png

然后開始如”下回收過程:
第一階段,掃描根。
根是指static變量指向的對象,正在執(zhí)行的方法調(diào)用鏈條上的局部變量等。根引用連同RSet記錄的外部引用作為掃描存活對象的入口。
第二階段,更新RSet。
處理dirty card queue( 見備注)中的card,更新RSet。此階段完成后,RSet可以準(zhǔn)確的反映老年代對所在的內(nèi)存分段中對象的引用
第三階段,處理RSet。
識別被老年代對象指向的Eden中的對象,這些被指向的Eden中的對象被認(rèn)為是存活的對象。
第四階段,復(fù)制對象。
此階段,對象樹被遍歷,Eden區(qū)內(nèi)存段中存活的對象會被復(fù)制到Survivor區(qū)中空的內(nèi)存分段Survivor區(qū)內(nèi)存段中存活的對象如果年齡未達(dá)闕值,年齡會加1,達(dá)到閥值會被會被復(fù)制到Old區(qū)中空的內(nèi)存分段。如果Survivor空間不夠,Eden空間的部分?jǐn)?shù)據(jù)會直接晉升到老年代空間。.
第五階段,處理引用。
處理Soft,Weak,Phantom, Final, JNI Weak等引用。最終Eden空間的數(shù)據(jù)為空,GC停止工作,而目標(biāo)內(nèi)存中的對象都是連續(xù)存儲的,沒有碎片,所以復(fù)制過程可以達(dá)到內(nèi)存整理的效果,減少碎片。

備注:對于應(yīng)用程序的引用賦值語句object.field=object,JVM會在之前和之后執(zhí)行特殊的操作以在dirty card queue中入隊(duì)-一個(gè)保存 了對象引用信息的card.在年輕代回收的時(shí)候,G1會對Dirty Card Queue中所有的card進(jìn)行處理,以更新RSet ,保證RSet實(shí)時(shí)準(zhǔn)確的反映引用關(guān)系。
那為什么不在引用賦值語句處直接更新RSet呢?這是為了性能的需樓,RSet的處理需要線程同步,開銷會很大,使用隊(duì)列性能會好很多。

回收過程二:并發(fā)標(biāo)記過程

1.初始標(biāo)記階段:標(biāo)記從根節(jié)點(diǎn)直接可達(dá)的對象。這個(gè)階段是STW的,并且會觸發(fā)一次年輕代GC。
2.根區(qū)域掃描(Root Region Scanning) :G1 GC掃描Survivor區(qū)直接可達(dá)的老年代區(qū)域?qū)ο螅?biāo)記被引用的對象。這一過程必須在young GC之前完成。
3.并發(fā)標(biāo)記(Concurrent Marking):在整個(gè)堆中進(jìn)行并發(fā)標(biāo)記(和應(yīng)用程序并發(fā)執(zhí)行),此過程可能被young GC中斷。在并發(fā)標(biāo)記階段,若發(fā)現(xiàn)區(qū)域?qū)ο笾械乃袑ο蠖际抢沁@個(gè)區(qū)域會被立即回收。同時(shí),并發(fā)標(biāo)記過程中,會計(jì)算每個(gè)區(qū)域的對象活性(區(qū)域中.存活對象的比例)。
4.再次標(biāo)記(Remark):由 于應(yīng)用程序持續(xù)進(jìn)行,需要修正上-一次的標(biāo)記結(jié)果。是STW的。G1中采用了比CMS更快的初始快照算法: snapshot-at-the-beginning (SATB)。
5.獨(dú)占清理(cleanup, STW):計(jì)算各個(gè)區(qū)域的存活對象和GC回收比例,并進(jìn)行排序,識別可以混合回收的區(qū)域。為下階段做鋪墊。是STW的。
?????這個(gè)階段并不會實(shí)際上去做垃圾的收集
6.并發(fā)清理階段:識別并清理完全空閑的區(qū)域。

回收過程三:混合回收

當(dāng)越來越多的對象晉升到老年代oldregion時(shí),為了避免堆內(nèi)存被耗盡,虛擬機(jī)會觸發(fā)一個(gè)混合的垃圾收集器,即Mixed GC,該算法并不是一個(gè)OldGC,除了回收整個(gè)Young Region,還會回收一部分的0ld Region. 這里需要注意:是一部分老年代, 而不
是全部老年代。可以選擇哪些0ldRegion進(jìn)行收集,從而可以對垃圾回收的耗時(shí)時(shí)間進(jìn)行控制。也要注意的是Mixed GC并不是Full GC。


image.png
  • 并發(fā)標(biāo)記結(jié)束以后,老年代中百分百為垃圾的內(nèi)存分段被回收了,部分為垃圾的內(nèi)存分段被計(jì)算了出來。默認(rèn)情況下,這些老年代的內(nèi)存分段會分8次(可以通過-
    XX: G1MixedGCCountTarget設(shè)置)被回收。
  • 混合回收的回收集(Collection Set) 包括八分之-的老年代內(nèi)存分段,Eden區(qū) 內(nèi)存
    分段,Survivor區(qū) 內(nèi)存分段?;旌匣厥盏乃惴ê湍贻p代回收的算法完全一- 樣,只是回收集多了老年代的內(nèi)存分段。具體過程請參考上面的年輕代回收過程。
  • 由于老年代中的內(nèi)存分段默認(rèn)分8次回收,61 會優(yōu)先回收垃圾多的內(nèi)存分段。垃圾占內(nèi)存分段比例越高的,越會被先回收。并且有一個(gè)閾值會決定內(nèi)存分段是否被回收,-XX: G1MixedGCL iveThresholdPercent,默認(rèn)為65%,意思是垃圾占內(nèi)存分段比例要達(dá)到65%才會被回收。如果垃圾占比太低,意味著存活的對象占比高,在復(fù)制的時(shí)候會花費(fèi)更多的時(shí)間。
  • 混合回收并不一定要進(jìn)行8次。有一一個(gè)閾值-XX:G1He apWastePercent,默認(rèn)值為10%,意思是允許整個(gè)堆內(nèi)存中有10%的空間被浪費(fèi),意味著如果發(fā)現(xiàn)可以回收的垃圾占堆內(nèi)存的比例低于10%,則不再進(jìn)行混合回收。因?yàn)镚C會花費(fèi)很多的時(shí)間但是回收到的內(nèi)存卻很少。
回收可選過程四:Full GC

G1的初衷就是要避免Full GC的出現(xiàn)。但是如果上述方式不能正常工作,G1會停止應(yīng)用程序的執(zhí)行(stop- The -World),使用單線程的內(nèi)存回收算法進(jìn)行垃圾回收,性能會非常差,應(yīng)用程序停頓時(shí)間會很長。要避免Full GC的發(fā)生,一旦發(fā)生需要進(jìn)行調(diào)整。什么時(shí)候會發(fā)生Full GC呢?比如堆內(nèi)存太小,當(dāng)G1在復(fù)制存活對象的時(shí)候沒有空的內(nèi)存分段可用,則會回退到full gc,這種情況可以通過增大內(nèi)存解決。導(dǎo)致G1Full GC的原因可能有兩個(gè):

  1. Evacuation的時(shí)候沒有足夠的to-space來存放晉升的對象;
  2. 并發(fā)處理過程完成之前空間耗盡。.

優(yōu)化建議:

  • 年輕代大小:
    • 避免使用-Xmn或-XX:NewRation等選項(xiàng)顯示設(shè)置年輕代大小
    • 固定年輕代的大小會覆蓋暫停目標(biāo)
  • 暫停時(shí)間目標(biāo)不要太過嚴(yán)苛
    • G1 GC的吞吐量目標(biāo)是90%的應(yīng)用程序時(shí)間和10%的垃圾回收時(shí)間
    • 評估G1 GC的吞吐量時(shí),暫停時(shí)間目標(biāo)不要太嚴(yán)苛,目標(biāo)太過于嚴(yán)苛表示你愿意承受更多的垃圾回收開銷,而這些會直接影響到吞吐量。

垃圾回收器總結(jié)

七種收集器


發(fā)展進(jìn)程

組合使用

怎么選擇垃圾回收器
1.優(yōu)先調(diào)整堆的大小讓JVM自適應(yīng)完成。
2.如果內(nèi)存小于100M, 使用串行收集器
3.如果是單核、單機(jī)程序,并且沒有停頓時(shí)間的要求,串行收集器
4.如果是多CPU、需要高吞吐量、允許停頓時(shí)間超過1秒,選擇并行或者JVM自己選擇
5.如果是多CPU、追求低停頓時(shí)間,需快速響應(yīng)(比如延遲不能超過1秒,如互
聯(lián)網(wǎng)應(yīng)用),使用并發(fā)收集器。
官方推薦G1,性能高?,F(xiàn)在互聯(lián)網(wǎng)的項(xiàng)目,基本都是使用G1。

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

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