JVM那點事-垃圾收集器(1次10ms的GC和10次1ms的GC,你會選哪個?)

小胖在寫完這篇文章后,問自己的幾個問題。我想,看完這篇文章,你可能就會有自己的答案了。

  1. 1次10msGC和10次1ms的GC你會選擇哪種?
  2. CMS收集器缺陷是什么?
  3. 提高CMS的啟動閾值一定能提高性能嗎?為什么?
  4. G1收集器為什么可以設(shè)置停頓時間?
  5. GC如何確定對象是否可被回收?
  6. 針對你說的“可達(dá)性分析法”,Minor GC時會掃描整個堆嗎?
  7. JDK8默認(rèn)的垃圾收集器是什么?

JVM那點事-垃圾收集算法講了GC垃圾回收算法,即(分代收集算法[復(fù)制算法——標(biāo)記清除/標(biāo)記整理])。我們接下來講一下垃圾回收器。下圖展示了不同分代的收集器,如果兩個收集器之間存在連線,說明他們可以搭配使用。虛擬機所在的區(qū)域,則表示它是屬于新生代收集器還是老年代收集器。

HotSpot垃圾回收器的種類.png

1. Serial收集器(單線程收集器)

標(biāo)簽:新生代收集器 復(fù)制算法 單線程收集器

Serial收集器[?s?ri?l]單線程新生代收集器,進行垃圾回收的時候,Stop the World暫停其他所有的工作線程。對于單個CPU的環(huán)境來說,簡單而高效。(虛擬機運行在Client模式下的默認(rèn)新生代收集器。)

2. ParNew收集器(并行收集器)

標(biāo)簽:新生代收集器 復(fù)制算法 多線程收集器 CMS默認(rèn)收集器

ParNew([?p?r])收集器其實就是Serial收集器的多線程版本。ParNew是許多運行在Server模式下虛擬機首選的新生代收集器。一個重要原因是:目前除了Serial收集器外,只有它與CMS收集器配合工作。ParNew收集器也是使用-XX:UseConcMarkSweepGC選項的默認(rèn)收集器,也可以使用-XX:+UseParNewGC選項強制指定。

ParNew收集器在單個CPU環(huán)境中絕對不會有比Serial收集器更好的效果。默認(rèn)開啟的收集器線程數(shù)與CPU的數(shù)量相同??梢允褂?code>-XX:ParallelGCThreads參數(shù)限制垃圾收集器的線程數(shù)[?p?r?lel]

并行(Parallel):指多條垃圾收集器線程并行工作,但此時用戶線程仍處于等待狀態(tài)。
并發(fā)(Concurrent):指用戶線程與垃圾回收器同時執(zhí)行(但不一定是并行的,可能會交替運行),用戶程序在繼續(xù)運行,而垃圾手機程序運行在另一個CPU上。

3. Parallel Scavenge收集器(并行收集器)

標(biāo)簽:新生代收集器 復(fù)制算法 多線程收集器 吞吐量優(yōu)先

[?p?r?lel][?sk?v?nd?] 并發(fā)清掃;Parallel Scavenge目標(biāo)是達(dá)到一個可控制的吞吐量。(吞吐量=用戶代碼運行時間/虛擬機運行時間,例如程序運行了100分鐘,GC占用1分鐘,那么吞吐量就是99%)

敲黑板,畫重點:面試官問道:“小胖同學(xué),1次10ms的GC和10次1ms的GC,你會選哪種?”)

  • 停頓時間短:適合與 用戶交互的程序,良好的響應(yīng)速度提升用戶體驗。
  • 高吞吐量:適合后臺運算而不需要太多交互的任務(wù),可以高效率利用CPU時間,盡快完成程序的運行任務(wù).

(小胖內(nèi)心OS:停頓時間1ms,這么快,一定是垃圾少,那就是犧牲了新生代空間頻繁GC會導(dǎo)致吞吐量的下降,但是適合用戶交互程序,反之,吞吐量大的話,可以高效利用CPU,適合后臺運算的程序。)

Parallel Scavenge收集器提供了兩個參數(shù)用于精確控制吞吐量??刂评厥掌魍nD時間的-XX:MaxGCPauseMills
[p?:z]['m?lz]。以及設(shè)置吞吐量大小的-XX:GCTimeRatio
不要以為把MaxGCPauseMills參數(shù)設(shè)置小就能使系統(tǒng)垃圾收集速度變快。GC停頓時間縮短是犧牲吞吐量和新生代空間來換取的。(比如將新生代調(diào)小,原來10s收集一次,每次100ms,;現(xiàn)在5s收集一次,每次70ms;停頓時間的確下降,但吞吐量也下降了)
GCTimeRatio參數(shù)的值應(yīng)當(dāng)是[0,100]的整數(shù)。如果把參數(shù)設(shè)置為19,允許最大的GC時間(1/(1+19))占用總時間的5%

4. Parallel Old收集器(并行收集器)

標(biāo)簽:老生代收集器 標(biāo)記-整理算法 多線程收集器 吞吐量優(yōu)先

注重吞吐量以及CPU資源敏感的場合,都可以優(yōu)先考慮Parallel ScavengeParallel Old收集器。

5. CMS收集器(并發(fā)收集器)

標(biāo)簽: 老年代收集器 標(biāo)記-清除算法 低停頓時間 并發(fā)收集器 CPU資源敏感 浮動垃圾 內(nèi)存碎片

CMS(Concurrent Mark Sweep)收集器是一種獲取最短回收停頓時間為目標(biāo)的收集器。目前很大一部分的java應(yīng)用集中在互聯(lián)網(wǎng)或者B/S系統(tǒng)的服務(wù)端上。重視響應(yīng)速度,希望系統(tǒng)停頓時間最短。

  • 初始標(biāo)記:標(biāo)記GC Roots 直接關(guān)聯(lián)的對象(Stop the World)。
  • 并發(fā)標(biāo)記:獲取初始標(biāo)記的節(jié)點做為根節(jié)點,并發(fā)標(biāo)記對象。
  • 重新標(biāo)記:修正并發(fā)標(biāo)記過程中變動的對象。如何修改?就是將并發(fā)標(biāo)記階段變化的對象記錄在線程Remembered Set Logs線程,在重新標(biāo)記階段,將數(shù)據(jù)合并到Remember Set中。(PS:詳見G1收集器描述)(Stop the World)
  • 并發(fā)清除并發(fā)清除對象

(原理簡單明了)

其中初始標(biāo)記重新標(biāo)記兩個步驟仍然需要Stop The World。

在Java語言中,可作為GC Roots的對象...

缺點是:

  • 對CPU資源敏感:在并發(fā)階段,雖然不會導(dǎo)致用戶線程停頓,但是占用線程(CPU資源)導(dǎo)致應(yīng)用程序變慢。CMS默認(rèn)啟動的線程數(shù)(CPU+3)/4,也就是當(dāng)CPU在4個以上時,并發(fā)回收垃圾線程不少于25%的CPU資源。

  • 浮動垃圾:并發(fā)清理階段用戶線程還在運行,只能下次GC回收,這些就稱為“浮動垃圾”。垃圾收集階段用戶線程還在運行,所以不能等到老年代幾乎填滿在進行收集。要設(shè)置老年代預(yù)留空間,可以設(shè)置-XX:CMSInitiatingOccupancyFranction音標(biāo): [?'n???e?t??]初始 [??kj?p?nsi] 居住 [?fr?k?n] 一小部分,提高觸發(fā)百分比。在JDK1.6,CMS收集器的啟動闕值92%。要是CMS運行期間預(yù)留的內(nèi)存無法滿足程序需要,就會觸發(fā)Concurrent Mode Failure 失敗,虛擬機啟動后備方案,臨時啟動Serial Old收集器來重新進行老年代的垃圾回收。所以說,參數(shù)-XX:CMSInitiatingOccupancyFranction設(shè)置的太高,容易觸發(fā)Concurrent Mode Failure失敗,性能反而降低。

  • 內(nèi)存碎片:“標(biāo)記-清除”算法會產(chǎn)生大量空間碎片!空間碎片過多時,大對象分配帶來麻煩,導(dǎo)致對象有很大剩余,但是無法找到連續(xù)空間分配對象,提前觸發(fā)full GC。
    可以設(shè)置-XX:UseCMSCompactAtFullCollectionCMS默認(rèn)開啟。在Full GC的時候采用合并整理過程,但是內(nèi)存整理無法并發(fā)會導(dǎo)致停頓時間變長。還有一個參數(shù)-XX:CMSFullGCsBeforeCompaction,這個參數(shù)是用于執(zhí)行多少次不壓縮的Full GC跟著來一次壓縮的。

總結(jié)來說:CMS并發(fā)收集,低停頓。

  1. 搶占CPU資源;
  2. 浮動垃圾,并且要為應(yīng)用程序預(yù)留空間,我們可以設(shè)置預(yù)留比例,但是預(yù)留比例無法滿足應(yīng)用程序需求時,會啟動Serial Old收集器。性能可能下降;
  3. 內(nèi)存碎片,可以開啟在清除后合并壓縮,但是整理階段,無法并發(fā)。導(dǎo)致停頓時間變長,也可以設(shè)置多次Full GC后壓縮碎片;

5. G1收集器(并發(fā)收集器)

標(biāo)簽: 分代收集 空間整合 并發(fā)收集 可預(yù)測停頓 內(nèi)存化整為零

G1之前的其他收集器收集的范圍都是新生代或者老年代。G1將整個Java堆劃分為大小相等的獨立區(qū)域·(region [?ri:d??n])。新生代老年代不再是物理隔離的了。他們都是一部分的region(不需要連續(xù))的集合。

G1收集器之所以能建立可預(yù)測的停頓時間模型。是因為G1可以跟蹤各個region里面垃圾堆價值大?。ɑ厥账@得的空間以及回收所需的時間經(jīng)驗值),在后臺維護了優(yōu)先列表。根據(jù)允許的收集時間,優(yōu)先回收價值最大的Region。(劃分空間以及有優(yōu)先級的區(qū)域回收。PS:G1執(zhí)行的第四步,篩選階段,就是根據(jù)用戶期望GC時間,制定回收計劃?。?/em>

一個對象被分配到region中,他并非只能被region中的其他對象引用,而是可以與整個java堆任意對象發(fā)生引用,那么可達(dá)性分析法判定對象是否存活時,豈不是還要掃描整個堆?在以前的分代收集中,新生代中的對象也面臨著相同的問題,Minor GC時若是同時掃描老年代的話,那么Minor GC效率可能下降不少。

G1垃圾回收是否掃描整個堆,或者minor gc是否掃描整個堆?

在G1收集器中,Region之間的對象引用以及其他收集器新生代和老年代之間的對象引用。(敲黑板,劃重點)虛擬機都是使用Remembered Set來避免全堆掃描的。

G1中每個region都有一個與之對應(yīng)的Remembered Set,虛擬機發(fā)現(xiàn)程序在對Reference類型的數(shù)據(jù)進行寫操作時,會產(chǎn)生一個Write Barrier暫停暫時中斷寫操作,檢查Reference引用對象是否處于不同的Region之中(分代中就是檢查是否老年代中的對象引用新生代的對象)。如果是,便通過CardTable把相關(guān)的引用對象所屬的RegionRemembered Set之中,當(dāng)進行垃圾回收時,在GC Roots中的枚舉范圍中加入Remembered Set即可保證不對全堆掃描也不會有遺漏。

小胖有話說:虛擬機對引用類型數(shù)據(jù)進行操作時,判斷引用的對象是否處于不同的Rigion區(qū),或者是否是老年代對象引用新生代對象,若是的話,則寫入Remembered Set中,GC Roots會在枚舉范圍加入Remembered Set保證不進行全表掃描的情況下不會遺漏對象。

(嘻嘻,以后面試官問G1執(zhí)行步驟時,將維護 Remembered Set操作也可以描述一下呀)

G1收集器運作大致可劃分為以下幾個步驟:

  • 初始標(biāo)記(Initial Marking):暫停線程,標(biāo)記GC Roots能直接關(guān)聯(lián)到的對象。

  • 并發(fā)階段(Concurrent Marking):對堆中的對象進行可達(dá)性分析,耗時較長,可并發(fā)執(zhí)行;

  • 最終階段(Final Marking):修正并發(fā)階段期間用戶程序運行導(dǎo)致標(biāo)記產(chǎn)生變化的記錄;* (敲黑板,繼續(xù)劃重點)*虛擬機將這段時間對象變化在線程Remembered Set Logs中,最終階段需要把Remembered Set Logs數(shù)據(jù)合并到Remembered Set中,需要停頓線程,但可并行執(zhí)行

  • 篩選階段(Live Data Counting and Evacuation):根據(jù)用戶期望的GC停頓時間制定回收計劃。也可做到與用戶程序并行執(zhí)行。

最后編輯于
?著作權(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)容