經(jīng)典垃圾收集器
如果說收集算法是內(nèi)存回收的方法論陪蜻,那垃圾收集器就是內(nèi)存回收的實(shí)踐者捆毫。
并行和并發(fā)都是并發(fā)編程中的專業(yè)名詞矩屁,在談?wù)摾占鞯纳舷挛恼Z(yǔ)境中,它們可以理解為:
·并行(Parallel):并行描述的是多條垃圾收集器線程之間的關(guān)系赊豌,說明同一時(shí)間有多條這樣的線程在協(xié)同工作,通常默認(rèn)此時(shí)用戶線程是處于等待狀態(tài)绵咱。
·并發(fā)(Concurrent):并發(fā)描述的是垃圾收集器線程與用戶線程之間的關(guān)系碘饼,說明同一時(shí)間垃圾收集器線程與用戶線程都在運(yùn)行。由于用戶線程并未被凍結(jié),所以程序仍然能響應(yīng)服務(wù)請(qǐng)求派昧,但由于垃圾收集器線程占用了一部分系統(tǒng)資源黔姜,此時(shí)應(yīng)用程序的處理的吞吐量將受到一定影響。
上圖七種作用于不同分代的收集器蒂萎,如果兩個(gè)收集器之間存在連線秆吵,就說明它們可以搭配使用。(注:serial與cms五慈、 parnew與serial old的組合在jdk8中聲明過期并于jdk9中正式廢棄纳寂。)? ?雖然垃圾收集器的技術(shù)在不斷進(jìn)步,但直到現(xiàn)在還沒有最好的收集器出現(xiàn)泻拦,更加不存在“萬(wàn)能”的收集器毙芜,所以我們選擇的只是對(duì)具體應(yīng)用最合適的收集器。
三款新生代收集器
Serial收集器——是最基礎(chǔ)争拐、歷史最悠久的收集器 (客戶端)
這個(gè)收集器是一個(gè)單線程工作的收集器腋粥,但它的“單線程”的意義并不僅僅是說明它只會(huì)使用一個(gè)處理器或一條收集線程去完成垃圾收集工作,更重要的是強(qiáng)調(diào)在它進(jìn)行垃圾收集時(shí)架曹,必須暫停其他所有工作線程隘冲,直到它收集結(jié)束——“Stop The World”。
迄今為止绑雄,它依然是HotSpot虛擬機(jī)運(yùn)行在客戶端模式下的默認(rèn)新生代收集器展辞,有著優(yōu)于其他收集器的地方,那就是簡(jiǎn)單而高效(與其他收集器的單線程相比)万牺,對(duì)于內(nèi)存資源受限的環(huán)境罗珍,它是所有收集器里額外內(nèi)存消耗(Memory Footprint)最小的;對(duì)于單核處理器或處理器核心數(shù)較少的環(huán)境來說脚粟,Serial收集器由于沒有線程交互的開銷覆旱,專心做垃圾收集自然可以獲得最高的單線程收集效率。在用戶桌面的應(yīng)用場(chǎng)景以及近年來流行的部分微服務(wù)應(yīng)用中核无,分配給虛擬機(jī)管理的內(nèi)存一般來說并不會(huì)特別大通殃,收集幾十兆甚至一兩百兆的新生代(僅僅是指新生代使用的內(nèi)存,桌面應(yīng)用甚少超過這個(gè)容量)厕宗,垃圾收集的停頓時(shí)間完全可以控制在十幾画舌、幾十毫秒,最多一百多毫秒以內(nèi)已慢,只要不是頻繁發(fā)生收集曲聂,這點(diǎn)停頓時(shí)間對(duì)許多用戶來說是完全可以接受的。所以佑惠,Serial收集器對(duì)于運(yùn)行在客戶端模式下的虛擬機(jī)來說是一個(gè)很好的選擇朋腋。
ParNew收集器——實(shí)質(zhì)上是Serial收集器的多線程并行版本? (服務(wù)端)
除了同時(shí)使用多條線程進(jìn)行垃圾收集之外齐疙,其余的行為包括Serial收集器可用的所有控制參數(shù)(例如:-XX:SurvivorRatio、-XX:PretenureSizeThreshold旭咽、-XX:HandlePromotionFailure等)贞奋、收集算法、Stop The World穷绵、對(duì)象分配規(guī)則轿塔、回收策略等都與Serial收集器完全一致,在實(shí)現(xiàn)上這兩種收集器也共用了相當(dāng)多的代碼仲墨。
它卻是不少運(yùn)行在服務(wù)端模式下的HotSpot虛擬機(jī)勾缭,尤其是JDK 7之前的遺留系統(tǒng)中首選的新生代收集器,其中有一個(gè)與功能目养、性能無(wú)關(guān)但其實(shí)很重要的原因是:除了Serial收集器外俩由,目前只有它能與CMS收集器配合工作。ParNew收集器是激活CMS后(使用-XX:+UseConcMarkSweepGC選項(xiàng))的默認(rèn)新生代收集器癌蚁,也可以使用-XX:+/-UseParNewGC選項(xiàng)來強(qiáng)制指定或者禁用它幻梯。
JDK 9開始,ParNew加CMS收集器的組合就不再是官方推薦的服務(wù)端模式下的收集器解決方案了努释。官方希望它能完全被G1所取代礼旅,甚至還取消了ParNew加SerialOld以及Serial加CMS這兩組收集器組合的支持(其實(shí)原本也很少人這樣使用),并直接取消了-XX:+UseParNewGC參數(shù)洽洁,這意味著ParNew和CMS從此只能互相搭配使用,再也沒有其他收集器能夠和它們配合了菲嘴。讀者也可以理解為從此以后饿自,ParNew合并入CMS,成為它專門處理新生代的組成部分龄坪。ParNew可以說是HotSpot虛擬機(jī)中第一款退出歷史舞臺(tái)的垃圾收集器昭雌。
Parallel Scavenge收集器(吞吐量?jī)?yōu)先收集器)——關(guān)注點(diǎn)在于吞吐量,其他收集器關(guān)注點(diǎn)是停頓時(shí)間
Parallel Scavenge收集器也是一款新生代收集器健田,它同樣是基于標(biāo)記-復(fù)制算法實(shí)現(xiàn)的收集器烛卧,也是能夠并行收集的多線程收集器。
Parallel Scavenge收集器的特點(diǎn)是它的關(guān)注點(diǎn)與其他收集器不同妓局,CMS等收集器的關(guān)注點(diǎn)是盡可能地縮短垃圾收集時(shí)用戶線程的停頓時(shí)間总放,而Parallel Scavenge收集器的目標(biāo)則是達(dá)到一個(gè)可控制的吞吐量(Throughput)。所謂吞吐量就是處理器用于運(yùn)行用戶代碼的時(shí)間與處理器總消耗時(shí)間的比值好爬,即:
停頓時(shí)間越短就越適合需要與用戶交互或需要保證服務(wù)響應(yīng)質(zhì)量的程序局雄,良好的響應(yīng)速度能提升用戶體驗(yàn);而高吞吐量則可以最高效率地利用處理器資源存炮,盡快完成程序的運(yùn)算任務(wù)炬搭,主要適合在后臺(tái)運(yùn)算而不需要太多交互的分析任務(wù)蜈漓。
三款老年代垃圾收集器
Serial Old收集器——Serial Old是Serial收集器的老年代版本
它同樣是一個(gè)單線程收集器,使用標(biāo)記-整理算法宫盔。這個(gè)收集器的主要意義也是供客戶端模式下的HotSpot虛擬機(jī)使用融虽。
如果在服務(wù)端模式下,它也可能有兩種用途:一種是在JDK 5以及之前的版本中與Parallel Scavenge收集器搭配使用灼芭,另外一種就是作為CMS收集器發(fā)生失敗時(shí)的后備預(yù)案有额,在并發(fā)收集發(fā)生Concurrent Mode Failure時(shí)使用。
Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本姿鸿,支持多線程并發(fā)收集谆吴,基于標(biāo)記-整理算法實(shí)現(xiàn)。
直到Parallel Old收集器出現(xiàn)后苛预,“吞吐量?jī)?yōu)先”收集器終于有了比較名副其實(shí)的搭配組合句狼,在注重吞吐量或者處理器資源較為稀缺的場(chǎng)合,都可以優(yōu)先考慮Parallel Scavenge加Parallel Old收集器這個(gè)組合热某。Parallel Old收集器的工作過程如下圖:
CMS收集器(并發(fā)低停頓收集器)——基于標(biāo)記-清除算法實(shí)現(xiàn)
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器腻菇。
目前很大一部分的Java應(yīng)用集中在互聯(lián)網(wǎng)網(wǎng)站或者基于瀏覽器的B/S系統(tǒng)的服務(wù)端上,這類應(yīng)用通常都會(huì)較為關(guān)注服務(wù)的響應(yīng)速度昔馋,希望系統(tǒng)停頓時(shí)間盡可能短筹吐,以給用戶帶來良好的交互體驗(yàn)。CMS收集器就非常符合這類應(yīng)用的需求秘遏。
整個(gè)過程分為四個(gè)步驟丘薛,包括:1)初始標(biāo)記(CMS initial mark)2)并發(fā)標(biāo)記(CMS concurrent mark)3)重新標(biāo)記(CMS remark)4)并發(fā)清除(CMS concurrent sweep)
其中初始標(biāo)記、重新標(biāo)記這兩個(gè)步驟仍然需要“Stop The World”邦危。并發(fā)標(biāo)記階段就是從GC Roots的直接關(guān)聯(lián)對(duì)象開始遍歷整個(gè)對(duì)象圖的過程洋侨,這個(gè)過程耗時(shí)較長(zhǎng)但是不需要停頓用戶線程,可以與垃圾收集線程一起并發(fā)運(yùn)行倦蚪;而重新標(biāo)記階段則是為了修正并發(fā)標(biāo)記期間希坚,因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄,這個(gè)階段的停頓時(shí)間通常會(huì)比初始標(biāo)記階段稍長(zhǎng)一些陵且,但也遠(yuǎn)比并發(fā)標(biāo)記階段的時(shí)間短裁僧;最后是并發(fā)清除階段,清理刪除掉標(biāo)記階段判斷的已經(jīng)死亡的對(duì)象慕购,由于不需要移動(dòng)存活對(duì)象聊疲,所以這個(gè)階段也是可以與用戶線程同時(shí)并發(fā)的。
由于在整個(gè)過程中耗時(shí)最長(zhǎng)的并發(fā)標(biāo)記和并發(fā)清除階段中沪悲,垃圾收集器線程都可以與用戶線程一起工作售睹,所以從總體上來說,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的可训。
CMS是一款優(yōu)秀的收集器昌妹,它最主要的優(yōu)點(diǎn)在名字上已經(jīng)體現(xiàn)出來:并發(fā)收集捶枢、低停頓
CMS收集器是HotSpot虛擬機(jī)追求低停頓的第一次成功嘗試,但是它還遠(yuǎn)達(dá)不到完美的程度飞崖,至少有以下三個(gè)明顯的缺點(diǎn):
首先烂叔,CMS收集器對(duì)處理器資源非常敏感。
然后固歪,由于CMS收集器無(wú)法處理“浮動(dòng)垃圾”(Floating Garbage)蒜鸡,有可能出現(xiàn)“Con-current Mode Failure”失敗進(jìn)而導(dǎo)致另一次完全“Stop The World”的Full GC的產(chǎn)生。
最后,CMS是一款基于“標(biāo)記-清除”算法實(shí)現(xiàn)的收集器牢裳,這意味著收集結(jié)束時(shí)會(huì)有大量空間碎片產(chǎn)生逢防。
G1收集器
Garbage First收集器——全功能的垃圾收集器(Fully-Featured Garbage Collector)
到了JDK 8 Update 40的時(shí)候,G1提供并發(fā)的類卸載的支持蒲讯,補(bǔ)全了其計(jì)劃功能的最后一塊拼圖忘朝。這個(gè)版本以后的G1收集器才被Oracle官方稱為“全功能的垃圾收集器”(Fully-Featured Garbage Collector)。
G1是一款主要面向服務(wù)端應(yīng)用的垃圾收集器判帮。將內(nèi)存回收的“行為”與“實(shí)現(xiàn)”進(jìn)行分離局嘁。
在G1收集器出現(xiàn)之前的所有其他收集器,包括CMS在內(nèi)晦墙,垃圾收集的目標(biāo)范圍要么是整個(gè)新生代(Minor GC)悦昵,要么就是整個(gè)老年代(Major GC),再要么就是整個(gè)Java堆(Full GC)晌畅。而G1跳出了這個(gè)樊籠但指,它可以面向堆內(nèi)存任何部分來組成回收集(Collection Set,一般簡(jiǎn)稱CSet)進(jìn)行回收抗楔,衡量標(biāo)準(zhǔn)不再是它屬于哪個(gè)分代棋凳,而是哪塊內(nèi)存中存放的垃圾數(shù)量最多,回收收益最大谓谦,這就是G1收集器的MixedGC模式。
G1開創(chuàng)的基于Region的堆內(nèi)存布局是它能夠?qū)崿F(xiàn)這個(gè)目標(biāo)的關(guān)鍵贪婉。雖然G1也仍是遵循分代收集理論設(shè)計(jì)的反粥,但其堆內(nèi)存的布局與其他收集器有非常明顯的差異:G1不再堅(jiān)持固定大小以及固定數(shù)量的分代區(qū)域劃分,而是把連續(xù)的Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region)疲迂,每一個(gè)Region都可以根據(jù)需要才顿,扮演新生代的Eden空間、Survivor空間尤蒿,或者老年代空間郑气。收集器能夠?qū)Π缪莶煌巧腞egion采用不同的策略去處理,這樣無(wú)論是新創(chuàng)建的對(duì)象還是已經(jīng)存活了一段時(shí)間腰池、熬過多次收集的舊對(duì)象都能獲取很好的收集效果尾组。
Region中還有一類特殊的Humongous區(qū)域忙芒,專門用來存儲(chǔ)大對(duì)象。G1認(rèn)為只要大小超過了一個(gè)Region容量一半的對(duì)象即可判定為大對(duì)象讳侨。每個(gè)Region的大小可以通過參數(shù)-XX:G1HeapRegionSize設(shè)定呵萨,取值范圍為1MB~32MB,且應(yīng)為2的N次冪跨跨。而對(duì)于那些超過了整個(gè)Region容量的超級(jí)大對(duì)象潮峦,將會(huì)被存放在N個(gè)連續(xù)的Humongous Region之中,G1的大多數(shù)行為都把Humongous Region作為老年代的一部分來進(jìn)行看待勇婴。
雖然G1仍然保留新生代和老年代的概念忱嘹,但新生代和老年代不再是固定的了,它們都是一系列區(qū)域(不需要連續(xù))的動(dòng)態(tài)集合耕渴。G1收集器之所以能建立可預(yù)測(cè)的停頓時(shí)間模型拘悦,是因?yàn)樗鼘egion作為單次回收的最小單元,即每次收集到的內(nèi)存空間都是Region大小的整數(shù)倍萨螺,這樣可以有計(jì)劃地避免在整個(gè)Java堆中進(jìn)行全區(qū)域的垃圾收集窄做。更具體的處理思路是讓G1收集器去跟蹤各個(gè)Region里面的垃圾堆積的“價(jià)值”大小,價(jià)值即回收所獲得的空間大小以及回收所需時(shí)間的經(jīng)驗(yàn)值慰技,然后在后臺(tái)維護(hù)一個(gè)優(yōu)先級(jí)列表椭盏,每次根據(jù)用戶設(shè)定允許的收集停頓時(shí)間(使用參數(shù)-XX:MaxGCPauseMillis指定,默認(rèn)值是200毫秒)吻商,優(yōu)先處理回收價(jià)值收益最大的那些Region掏颊,這也就是“Garbage First”名字的由來。這種使用Region劃分內(nèi)存空間艾帐,以及具有優(yōu)先級(jí)的區(qū)域回收方式乌叶,保證了G1收集器在有限的時(shí)間內(nèi)獲取盡可能高的收集效率。
G1收集器的運(yùn)作過程大致可劃分為以下四個(gè)步驟:
·初始標(biāo)記(Initial Marking):僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象柒爸,并且修改TAMS指針的值准浴,讓下一階段用戶線程并發(fā)運(yùn)行時(shí),能正確地在可用的Region中分配新對(duì)象捎稚。這個(gè)階段需要停頓線程乐横,但耗時(shí)很短,而且是借用進(jìn)行Minor GC的時(shí)候同步完成的今野,所以G1收集器在這個(gè)階段實(shí)際并沒有額外的停頓葡公。
·并發(fā)標(biāo)記(Concurrent Marking):從GC Root開始對(duì)堆中對(duì)象進(jìn)行可達(dá)性分析,遞歸掃描整個(gè)堆里的對(duì)象圖条霜,找出要回收的對(duì)象催什,這階段耗時(shí)較長(zhǎng),但可與用戶程序并發(fā)執(zhí)行宰睡。當(dāng)對(duì)象圖掃描完成以后蒲凶,還要重新處理SATB記錄下的在并發(fā)時(shí)有引用變動(dòng)的對(duì)象气筋。
·最終標(biāo)記(Final Marking):對(duì)用戶線程做另一個(gè)短暫的暫停,用于處理并發(fā)階段結(jié)束后仍遺留下來的最后那少量的SATB記錄豹爹。
·篩選回收(Live Data Counting and Evacuation):負(fù)責(zé)更新Region的統(tǒng)計(jì)數(shù)據(jù)裆悄,對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶所期望的停頓時(shí)間來制定回收計(jì)劃臂聋,可以自由選擇任意多個(gè)Region構(gòu)成回收集光稼,然后把決定回收的那一部分Region的存活對(duì)象復(fù)制到空的Region中,再清理掉整個(gè)舊Region的全部空間孩等。這里的操作涉及存活對(duì)象的移動(dòng)艾君,是必須暫停用戶線程,由多條收集器線程并行完成的肄方。
從G1開始冰垄,最先進(jìn)的垃圾收集器的設(shè)計(jì)導(dǎo)向都不約而同地變?yōu)樽非竽軌驊?yīng)付應(yīng)用的內(nèi)存分配速率(Allocation Rate),而不追求一次把整個(gè)Java堆全部清理干凈权她。
相比CMS虹茶,G1的優(yōu)點(diǎn)有很多,暫且不論可以指定最大停頓時(shí)間隅要、分Region的內(nèi)存布局蝴罪、按收益動(dòng)態(tài)確定回收集這些創(chuàng)新性設(shè)計(jì)帶來的紅利,單從最傳統(tǒng)的算法理論上看步清,G1也更有發(fā)展?jié)摿σ拧EcCMS的“標(biāo)記-清除”算法不同,G1從整體來看是基于“標(biāo)記-整理”算法實(shí)現(xiàn)的收集器廓啊,但從局部(兩個(gè)Region之間)上看又是基于“標(biāo)記-復(fù)制”算法實(shí)現(xiàn)欢搜,無(wú)論如何,這兩種算法都意味著G1運(yùn)作期間不會(huì)產(chǎn)生內(nèi)存空間碎片谴轮,垃圾收集完成之后能提供規(guī)整的可用內(nèi)存炒瘟。這種特性有利于程序長(zhǎng)時(shí)間運(yùn)行,在程序?yàn)榇髮?duì)象分配內(nèi)存時(shí)不容易因無(wú)法找到連續(xù)內(nèi)存空間而提前觸發(fā)下一次收集第步。
目前在小內(nèi)存應(yīng)用上CMS的表現(xiàn)大概率仍然要會(huì)優(yōu)于G1疮装,而在大內(nèi)存應(yīng)用上G1則大多能發(fā)揮其優(yōu)勢(shì),這個(gè)優(yōu)劣勢(shì)的Java堆容量平衡點(diǎn)通常在6GB至8GB之間雌续。
低延遲垃圾收集器
衡量垃圾收集器的三項(xiàng)最重要的指標(biāo)是:內(nèi)存占用(Footprint)斩个、吞吐量(Throughput)和延遲(Latency)胯杭,三者共同構(gòu)成了一個(gè)“不可能三角”驯杜。
要在這三個(gè)方面同時(shí)具有卓越表現(xiàn)的“完美”收集器是極其困難甚至是不可能的,一款優(yōu)秀的收集器通常最多可以同時(shí)達(dá)成其中的兩項(xiàng)做个。
“低延遲垃圾收集器”(Low-Latency Garbage Collector或者Low-Pause-Time GarbageCollector)鸽心。