收集算法是內(nèi)存回收的方法論,那么收集器就是收集算法的實(shí)現(xiàn)洼专。
下圖展示了7種作用于不同分代的收集器棒掠,如果收集器之間存在連線,就說(shuō)明他們可以搭配使用屁商。虛擬機(jī)所處的區(qū)域烟很,則表示它是屬于新生代收集器還是老年代收集器。
Serial收集器-新生代收集器
Serial收集器是最基本蜡镶、最悠久的收集器雾袱。這個(gè)收集器是一個(gè)單線程收集器,在它進(jìn)行垃圾收集時(shí)官还,必須停掉所有其他的工作線程芹橡,然后以一條收集線程進(jìn)行垃圾收集,直到收集工作結(jié)束望伦,才可以恢復(fù)其他工作線程僻族。這對(duì)于許多應(yīng)用是難以接受的粘驰。但是對(duì)Client(客戶端)模式的虛擬機(jī)來(lái)說(shuō),Serial收集器是一個(gè)不錯(cuò)的選擇述么,因?yàn)樵谧烂娑藨?yīng)用蝌数,分配給虛擬機(jī)的內(nèi)存不會(huì)太大,收集幾十兆到幾百兆的新生代內(nèi)存停頓時(shí)間完全可以控制在幾十毫秒度秘。
參數(shù)控制:-XX:+UseSerialGC
串行收集器
ParNew收集器-新生代收集器
ParNew收集器其實(shí)就是Serial收集器的多線程版本顶伞,除了使用多協(xié)調(diào)線程進(jìn)行垃圾收集外,其余的Serial收集器完全一樣剑梳。ParNew收集器在單CPU或CPU數(shù)量少的環(huán)境中性能不會(huì)有比Serial收集器更好的結(jié)果唆貌,但是隨著CPU數(shù)量的增多,它GC時(shí)對(duì)CPU資源的的有效利用還是很有好處的垢乙,所以它是許多運(yùn)行在Server模式下的虛擬機(jī)的首先新生代收集器锨咙。
參數(shù)控制:
-XX:+UseParNewGC
ParNew收集器
-XX:ParallelGCThreads
限制線程數(shù)量
Parallel Scavenge收集器-新生代收集器
它看上去似乎與ParNew一樣,但是它的目標(biāo)是達(dá)到一個(gè)可控制的吞吐量(吞吐量 = 運(yùn)行用戶代碼時(shí)間 / (運(yùn)行用戶代碼時(shí)間 + GC時(shí)間))追逮。停頓時(shí)間越短酪刀,就越適合與用戶交互的程序,因?yàn)榱己玫捻憫?yīng)時(shí)間可以提高用戶的體驗(yàn)钮孵,而吞吐量則可以高效利用CPU時(shí)間盡快完成程序的計(jì)算任務(wù)骂倘,主要適合在后臺(tái)運(yùn)算而需要交互任務(wù)。
Parallel Old收集器-老年代收集器
Parallel Old是Parallel Scavenge收集器的老年代版本巴席,使用多線程和“標(biāo)記-整理”算法历涝。這個(gè)收集器是在JDK 1.6中才開始提供
參數(shù)控制: -XX:+UseParallelOldGC
使用Parallel收集器+ 老年代并行
CMS收集器-老年代收集器
CMS(Concurrent Mark Sweep)
收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。目前很大一部分的Java應(yīng)用都集中在互聯(lián)網(wǎng)站或B/S系統(tǒng)的服務(wù)端上漾唉,這類應(yīng)用尤其重視服務(wù)的響應(yīng)速度荧库,希望系統(tǒng)停頓時(shí)間最短,以給用戶帶來(lái)較好的體驗(yàn)赵刑。
從名字(包含Mark Sweep
)上就可以看出CMS收集器是基于“標(biāo)記-清除”算法實(shí)現(xiàn)的分衫,它的運(yùn)作過(guò)程相對(duì)于前面幾種收集器來(lái)說(shuō)要更復(fù)雜一些,整個(gè)過(guò)程分為4個(gè)步驟料睛,包括:
- 1丐箩、初始標(biāo)記(
CMS initial mark
):僅僅只是標(biāo)記一下GC Roots
能直接關(guān)聯(lián)到的對(duì)象,速度很快恤煞,需要停頓屎勘。 - 2、并發(fā)標(biāo)記(
CMS concurrent mark
):進(jìn)行GC Roots Tracing
的過(guò)程居扒,它在整個(gè)回收過(guò)程中耗時(shí)最長(zhǎng)概漱,不需要停頓。 - 3喜喂、重新標(biāo)記(
CMS remark
):為了修正并發(fā)標(biāo)記期間瓤摧,因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄竿裂,需要停頓。 - 4照弥、并發(fā)清除(
CMS concurrent sweep)
:不需要停頓腻异。
由于整個(gè)過(guò)程中耗時(shí)最長(zhǎng)的并發(fā)標(biāo)記和并發(fā)清除過(guò)程中,收集器線程都可以與用戶線程一起工作这揣,所以總體上來(lái)說(shuō)悔常,CMS收集器的內(nèi)存回收過(guò)程是與用戶線程一起并發(fā)地執(zhí)行。老年代收集器(新生代使用ParNew
)
優(yōu)點(diǎn): 并發(fā)收集给赞、低停頓
缺點(diǎn): 產(chǎn)生大量空間碎片机打、并發(fā)階段會(huì)降低吞吐量
參數(shù)控制:
-XX:+UseConcMarkSweepGC
使用CMS收集器
-XX:+ UseCMSCompactAtFullCollection
Full GC后,進(jìn)行一次碎片整理片迅;整理過(guò)程是獨(dú)占的残邀,會(huì)引起停頓時(shí)間變長(zhǎng)
-XX:+CMSFullGCsBeforeCompaction
設(shè)置進(jìn)行幾次Full GC后柑蛇,進(jìn)行一次碎片整理
-XX:ParallelCMSThreads
設(shè)定CMS的線程數(shù)量(一般情況約等于可用CPU數(shù)量)
G1收集器
G1是目前技術(shù)發(fā)展的最前沿成果之一芥挣,是面向服務(wù)端應(yīng)用的垃圾收集器。HotSpot開發(fā)團(tuán)隊(duì)賦予它的使命是未來(lái)可以替換掉JDK1.5中發(fā)布的CMS收集器唯蝶。與CMS收集器相比G1收集器有一下特點(diǎn):
- 1九秀、并行與并發(fā):能充分利用CPU環(huán)境下的硬件優(yōu)勢(shì)遗嗽,使用多個(gè)CPU來(lái)縮短停頓的時(shí)間粘我。
- 2、分代收集:雖然它不需要其它收集器配合就能獨(dú)立管理整個(gè)GC堆痹换,但它能夠采用不同方式去處理新創(chuàng)建的對(duì)象和已存活一段時(shí)間征字、熬過(guò)多次GC的舊對(duì)象來(lái)獲取更好的收集效果。
- 3娇豫、空間整合:整體來(lái)看是基于“標(biāo)記-整理”算法實(shí)現(xiàn)的收集器匙姜,從局部(兩個(gè)Region之間)上來(lái)看是基于“復(fù)制”算法實(shí)現(xiàn)的,這意味著運(yùn)行期間不會(huì)產(chǎn)生內(nèi)存空間碎片冯痢。
- 可預(yù)測(cè)停頓:這是它相對(duì)CMS的一大優(yōu)勢(shì)氮昧,降低停頓時(shí)間是G1和CMS共同的關(guān)注點(diǎn),但G1除了降低停頓外浦楣,還能建立可預(yù)測(cè)的停頓時(shí)間模型袖肥,能讓使用者明確指定在一個(gè)長(zhǎng)度為M毫秒的時(shí)間片段內(nèi),消耗在GC上的時(shí)間不能超過(guò)N毫秒振劳,這幾乎已經(jīng)是實(shí)時(shí)Java(RTSJ)的垃圾收集器的特征了椎组。
在 G1 之前的其他收集器進(jìn)行收集的范圍都是整個(gè)新生代或者老生代,而 G1 不再是這樣历恐,Java 堆的內(nèi)存布局與其他收集器有很大區(qū)別寸癌,將整個(gè) Java 堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region
)专筷。雖然還保留新生代和老年代的概念,但新生代和老年代不再是物理隔離的了蒸苇,而都是一部分Region
(不需要連續(xù))的集合磷蛹。
之所以能建立可預(yù)測(cè)的停頓時(shí)間模型,是因?yàn)樗梢杂杏?jì)劃地避免在整個(gè) Java 堆中進(jìn)行全區(qū)域的垃圾收集溪烤。它跟蹤各個(gè) Region
里面的垃圾堆積的價(jià)值大邢夷簟(回收所獲得的空間大小以及回收所需時(shí)間的經(jīng)驗(yàn)值),在后臺(tái)維護(hù)一個(gè)優(yōu)先列表氛什,每次根據(jù)允許的收集時(shí)間莺葫,優(yōu)先回收價(jià)值最大的 Region
(這也就是 Garbage-First
名稱的來(lái)由)。這種使用 Region 劃分內(nèi)存空間以及有優(yōu)先級(jí)的區(qū)域回收方式枪眉,保證了它在有限的時(shí)間內(nèi)可以獲取盡可能高的收集效率捺檬。
Region
不可能是孤立的,一個(gè)對(duì)象分配在某個(gè) Region
中贸铜,可以與整個(gè) Java 堆任意的對(duì)象發(fā)生引用關(guān)系堡纬。在做可達(dá)性分析確定對(duì)象是否存活的時(shí)候,需要掃描整個(gè) Java 堆才能保證準(zhǔn)確性蒿秦,這顯然是對(duì) GC 效率的極大傷害烤镐。為了避免全堆掃描的發(fā)生,每個(gè)Region
都維護(hù)了一個(gè)與之對(duì)應(yīng)的 Remembered Set
棍鳖。虛擬機(jī)發(fā)現(xiàn)程序在對(duì)Reference
類型的數(shù)據(jù)進(jìn)行寫操作時(shí)炮叶,會(huì)產(chǎn)生一個(gè)Write Barrier
暫時(shí)中斷寫操作,檢查 Reference
引用的對(duì)象是否處于不同的 Region
之中渡处,如果是镜悉,便通過(guò) CardTable
把相關(guān)引用信息記錄到被引用對(duì)象所屬的 Region
的Remembered Set
之中。當(dāng)進(jìn)行內(nèi)存回收時(shí)医瘫,在 GC 根節(jié)點(diǎn)的枚舉范圍中加入Remembered Set
即可保證不對(duì)全堆掃描也不會(huì)有遺漏侣肄。
七種垃圾收集器的比較
內(nèi)存分配與回收策略
對(duì)象的內(nèi)存分配,也就是在堆上分配醇份。主要分配在新生代的Eden區(qū)上稼锅,少數(shù)情況下也可以直接分配在老年代中。
1.優(yōu)先在Eden分配
大多數(shù)情況下僚纷,對(duì)象在新生代Eden區(qū)分配矩距,當(dāng)Eden區(qū)間不夠時(shí),發(fā)起Minor GC
畔濒。
從新生代(包括Eden和Survivor區(qū)域)回收內(nèi)存被稱為
Minor GC
剩晴。清理整個(gè)堆空間包括新生代和老年代被稱為
Full GC
。
關(guān)于Minor GC
和Full GC
:
-
Minor GC
:發(fā)生在新生代上,因?yàn)樾律鷮?duì)象存活時(shí)間很短赞弥,因此Minor GC
會(huì)頻繁執(zhí)行毅整,執(zhí)行的速度一般也會(huì)比較快。 -
Full GC
:發(fā)生在老年代上绽左,老年代對(duì)象和新生代對(duì)象相反悼嫉,其存活時(shí)間很長(zhǎng),因此Full GC
很少執(zhí)行拼窥,而且執(zhí)行速度會(huì)比Minor GC
慢很多戏蔑。
2.大對(duì)象直接進(jìn)入老年代
大對(duì)象是指需要連續(xù)內(nèi)存空間的對(duì)象,最典型的大對(duì)象是那種很長(zhǎng)的字符串以及數(shù)組鲁纠。經(jīng)常出現(xiàn)大對(duì)象會(huì)提前觸發(fā)垃圾收集以獲取足夠的連續(xù)空間分配給大對(duì)象总棵。
3.長(zhǎng)期存活的對(duì)象進(jìn)入老年代
虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡(Age)計(jì)數(shù)器,如果對(duì)象在Eden出生并經(jīng)過(guò)第一次Minor GC
后仍然存活改含,并且能被Survivor
區(qū)容納的話情龄,將被移動(dòng)到Survivor
空間中,并且對(duì)象年齡設(shè)為1捍壤。對(duì)象在Survivor
區(qū)中每“熬過(guò)”一次Minor GC
骤视,年齡就增加1歲,當(dāng)它的年齡增加到一定程度(默認(rèn)15歲)鹃觉,就將會(huì)被晉升到老年代中专酗。對(duì)象晉升老年代的年齡閾值,可以通過(guò)參數(shù)-XX:MaxTenuringThreshold
設(shè)置
4.動(dòng)態(tài)對(duì)象年齡判定
為了能更好的適應(yīng)不同程序的內(nèi)存狀況盗扇,虛擬機(jī)并不是永遠(yuǎn)地要求對(duì)象的年齡必須達(dá)到了MaxTenuringThreshold
才能晉升老年代祷肯,如果在Survivor
空間中相同年齡所有對(duì)象大小的總和大于Survivor
空間的一半,年齡大于或者等于該年齡的對(duì)象就可以直接進(jìn)入老年代粱玲,無(wú)須等到MaxTenuringThreshold
中要求的年齡躬柬。
5.空間分配擔(dān)保
在發(fā)生 Minor GC
之前拜轨,JVM 先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間抽减,如果條件成立的話,那么Minor GC
可以確認(rèn)是安全的橄碾;如果不成立的話 JVM 會(huì)查看 HandlePromotionFailure
設(shè)置值是否允許擔(dān)保失敗卵沉,如果允許那么就會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于法牲,將嘗試著進(jìn)行一次Minor GC
史汗,盡管這次 Minor GC
是有風(fēng)險(xiǎn)的;如果小于拒垃,或者 HandlePromotionFailure
設(shè)置不允許冒險(xiǎn)停撞,那這時(shí)也要改為進(jìn)行一次 Full GC
。
參考資料
https://github.com/Michaeljian/Interview-Notebook/blob/master/notes/JVM.md