小胖在寫完這篇文章后,問自己的幾個問題。我想,看完這篇文章,你可能就會有自己的答案了。
- 1次10msGC和10次1ms的GC你會選擇哪種?
- CMS收集器缺陷是什么?
- 提高CMS的啟動閾值一定能提高性能嗎?為什么?
- G1收集器為什么可以設(shè)置停頓時間?
- GC如何確定對象是否可被回收?
- 針對你說的“可達(dá)性分析法”,Minor GC時會掃描整個堆嗎?
- JDK8默認(rèn)的垃圾收集器是什么?
JVM那點事-垃圾收集算法講了GC垃圾回收算法,即(
分代收集算法[復(fù)制算法——標(biāo)記清除/標(biāo)記整理])。我們接下來講一下垃圾回收器。下圖展示了不同分代的收集器,如果兩個收集器之間存在連線,說明他們可以搭配使用。虛擬機所在的區(qū)域,則表示它是屬于新生代收集器還是老年代收集器。

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 Scavenge加Parallel 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。
缺點是:
對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ā)收集,低停頓。
- 搶占CPU資源;
- 浮動垃圾,并且要為
應(yīng)用程序預(yù)留空間,我們可以設(shè)置預(yù)留比例,但是預(yù)留比例無法滿足應(yīng)用程序需求時,會啟動Serial Old收集器。性能可能下降;- 內(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)的引用對象所屬的Region的Remembered 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í)行。