常用的GC算法
1.引用計(jì)數(shù)法
給一個(gè)對(duì)象添加一個(gè)引用計(jì)數(shù)器喝滞,每當(dāng)有一個(gè)地方引用它時(shí)猴誊,計(jì)數(shù)器的值就加1潦刃,當(dāng)引用失效時(shí)計(jì)數(shù)器就減1,當(dāng)計(jì)數(shù)器的值為0時(shí)懈叹,代表對(duì)象不再被使用乖杠,可以進(jìn)行回收。
特點(diǎn)
引用計(jì)數(shù)法實(shí)現(xiàn)簡(jiǎn)單澄成,判斷效率也很高胧洒,但是它很難解決對(duì)象之間互相引用的問題。如兩個(gè)對(duì)象objA和objB都有一個(gè)字段instance,
objA.instance=objB;
objB.instance=objA;
除了這里互相引用其余地方都沒有任何引用环揽,這兩個(gè)對(duì)象都是不可訪問的對(duì)象略荡,但他們的計(jì)數(shù)都不是0.引用計(jì)數(shù)法無法通知GC收集器收集庵佣。
2.可達(dá)性算法
這個(gè)算法的思想是通過一系列叫做“GC Root”的節(jié)點(diǎn)歉胶,從這些節(jié)點(diǎn)開始向下搜索,搜索到的路徑叫做引用鏈巴粪,當(dāng)一個(gè)對(duì)象到GC Root沒有任何引用鏈相連(圖論的角度通今,沒有從GC Root到該對(duì)象的路徑),表明該對(duì)象不可達(dá)肛根”杷可以被回收
GC Root對(duì)象
虛擬機(jī)棧(棧幀中的本地變量表)中引用的的變量
方法區(qū)類靜態(tài)屬性的變量
方法區(qū)常量引用的對(duì)象
本地方法棧中(即一般說的Native方法)引用的變量
標(biāo)記清除法
標(biāo)記清除法有兩個(gè)階段:標(biāo)記和清除
標(biāo)記階段:標(biāo)記階段會(huì)為所有活動(dòng)對(duì)象打上標(biāo)記,首先標(biāo)記通過根直接引用的對(duì)象派哲,然后遞歸的別偶記通過指針數(shù)組可以訪問的對(duì)象臼氨,有些函數(shù)可能會(huì)多次調(diào)用,為避免重復(fù)標(biāo)記芭届,每次標(biāo)記前會(huì)檢查是否已標(biāo)記储矩,若已標(biāo)記,則不處理褂乍。
清除階段:清除標(biāo)記階段標(biāo)記的對(duì)象
特點(diǎn)
1.標(biāo)記跟清除兩個(gè)階段效率都不高
2.存在內(nèi)存碎片問題
復(fù)制算法
為了解決標(biāo)記清除的效率問題持隧,產(chǎn)生了復(fù)制算法,首先將內(nèi)存分成大小相等的兩塊逃片,每次使用其中的一塊屡拨,當(dāng)內(nèi)存不夠發(fā)生GC時(shí),將存活的對(duì)象復(fù)制到未使用的一塊中,一次性清除已使用過的那一塊的所有呀狼。
特點(diǎn)
1.每次都對(duì)整個(gè)半?yún)^(qū)內(nèi)存進(jìn)行回收裂允,內(nèi)存分配也不需要考慮內(nèi)存碎片等復(fù)雜情況
2.實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效
3.代價(jià)是將內(nèi)存減少到了原來的一半
4.現(xiàn)在商業(yè)的虛擬機(jī)都采用復(fù)制算法對(duì)分代思想的新生代進(jìn)行收集哥艇。
5.當(dāng)存活率較高時(shí)叫胖,要復(fù)制的對(duì)象就很多,復(fù)制算法的效率將會(huì)變得很低她奥。
標(biāo)記整理算法
為了解決對(duì)象存活率比較高的情況下瓮增,復(fù)制算法效率變得很低的問題。還有如果不想浪費(fèi)50%的空間的話哩俭,為了應(yīng)對(duì)一些極端情況(如100%對(duì)象存活)就需要額外的內(nèi)存進(jìn)行擔(dān)保绷跑,(下面分代算法中的新生代采用復(fù)制算法,就需要老年代做擔(dān)保)凡资≡夷螅基于上面這些復(fù)雜算法的特點(diǎn),所以老年代一般不采用復(fù)制算法進(jìn)行垃圾回收隙赁,而是采用標(biāo)記整理算法垦藏。
標(biāo)記整理算法與標(biāo)記清除算法的區(qū)別是后續(xù)步驟不是對(duì)垃圾進(jìn)行直接的清除,而是將存活對(duì)象移向內(nèi)存的一端伞访,然后直接清除掉邊界外的垃圾內(nèi)容掂骏,如下圖所示
特點(diǎn)
1.在標(biāo)記-清除的基礎(chǔ)上還需進(jìn)行對(duì)象的移動(dòng),成本相對(duì)較高
2.不會(huì)產(chǎn)生內(nèi)存碎片厚掷。
3.一般在老年代的回收中會(huì)采用弟灼。(如CMS垃圾回收器可以設(shè)置在進(jìn)行多少次標(biāo)記清除算法之后可以進(jìn)行一次標(biāo)記整理算法)
分代算法
這種方法根據(jù)對(duì)象的存活周期不同,將內(nèi)存劃分為幾塊冒黑,一般將內(nèi)存分為新生代田绑、老年代。這樣可以根據(jù)不同年代的特點(diǎn)使用不同的回收算法抡爹,新生代一般都是朝生夕滅的對(duì)象掩驱,只有少量對(duì)象存活所以可以選擇復(fù)雜算法。而老年代對(duì)象的存活率較高冬竟,又沒有空間擔(dān)保欧穴,所以一般采用標(biāo)記清除或標(biāo)記整理算法。
收集器的種類
Serial收集器&Serial old收集器
Serial收集器是一款單線程收集器主要是用來對(duì)新生代進(jìn)行垃圾回收诱咏,采用的是復(fù)制算法苔可,與Serial old配合使用時(shí)Serial old對(duì)老年代進(jìn)行垃圾回收,采用標(biāo)記整理算法袋狞。
Serial收集器簡(jiǎn)單高效但停頓現(xiàn)象嚴(yán)重(Stop the world現(xiàn)象)焚辅,對(duì)于運(yùn)行在Client模式下的虛擬機(jī)是一種很好的選擇
ParNew收集器
ParNew收集器是Serial收集器的多線程版本映屋,也是新生代的一款收集器,采用復(fù)制算法同蜻,與Serial收集器相比除了采用多線程收集外棚点,沒有多大區(qū)別。還有一點(diǎn)需要注意湾蔓,除了Serial收集器瘫析,目前只有ParNew收集器能與CMS配合工作
ParNew收集器在單CPU環(huán)境中絕對(duì)沒有Serial的效果好,由于存在線程交互的開銷默责,該收集器在超線程技術(shù)實(shí)現(xiàn)的雙CPU中都不能一定超過Serial收集器贬循。默認(rèn)開啟的垃圾收集器線程數(shù)就是CPU數(shù)量,可通過-XX:parallelGCThreads參數(shù)來限制收集器線程數(shù)
Parallel Scavenge(并行回收GC)收集器
Parallel Scavenge(并行回收GC)收集器是一款新生代的桃序、采用復(fù)制算法的并行收集器杖虾,parallel Scavenge收集器的目標(biāo)則是達(dá)到一個(gè)可控制的吞吐量。吞吐量= 程序運(yùn)行時(shí)間/(程序運(yùn)行時(shí)間 + 垃圾收集時(shí)間)
短停頓時(shí)間適合和用戶交互的程序媒熊,體驗(yàn)好奇适。高吞吐量適合高效利用CPU,主要用于后臺(tái)運(yùn)算不需要太多交互芦鳍。
提供了兩個(gè)參數(shù)來精確控制吞吐量:1.最大垃圾收集器停頓時(shí)間(-XX:MaxGCPauseMillis 大于0的毫秒數(shù)嚷往,停頓時(shí)間小了就要犧牲相應(yīng)的吞吐量和新生代空間),2.設(shè)置吞吐量大心啤(-XX:GCTimeRatio 大于0小于100的整數(shù)皮仁,默認(rèn)99,也就是允許最大1%的垃圾回收時(shí)間)茄茁。
還有一個(gè)參數(shù)表示自適應(yīng)調(diào)節(jié)策略(GC Ergonomics)(-XX:UseAdaptiveSizePolicy)魂贬。就不用手動(dòng)設(shè)置新生代大泄睢(-Xmn)裙顽、Eden和Survivor區(qū)的比例(-XX:SurvivorRatio)新生老年代對(duì)象大小(-XX:PretenureSizeThreshold)宣谈,會(huì)根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況手機(jī)監(jiān)控信息愈犹,動(dòng)態(tài)調(diào)整停頓時(shí)間和吞吐量大小。也是其與PreNew收集器的一個(gè)重要區(qū)別闻丑,也是其無法與CMS收集器搭配使用的原因(CMS收集器盡可能地縮短垃圾收集時(shí)用戶線程的停頓時(shí)間漩怎,以提升交互體驗(yàn))。
Parallel Old(并行GC)收集器
Parallel Old是Parallel Scavenge收集器的老年代版本嗦嗡,使用多線程和“標(biāo)記-整理”算法勋锤,JDK1.6才提供。
由于之前有一個(gè)Parallel Scavenge新生代收集器侥祭,叁执,但是卻無老年代收集器與之完美結(jié)合茄厘,只能采用Serial Old老年代收集器,但是由于Serial Old收集器在服務(wù)端應(yīng)用性能上低下谈宛,其吞吐量反而不一定有PreNew+CMS組合次哈。
CMS(并發(fā)GC)收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。CMS收集器是HotSpot虛擬機(jī)中的一款真正意義上的并發(fā)收集器吆录,第一次實(shí)現(xiàn)了讓垃圾回收線程和用戶線程(基本上)同時(shí)工作窑滞。用CMS收集老年代的時(shí)候,新生代只能選擇Serial或者ParNew收集器恢筝。
CMS收集器是基于“標(biāo)記-清除”算法實(shí)現(xiàn)的哀卫,整個(gè)收集過程大致分為4個(gè)步驟:
①.初始標(biāo)記(CMS initial mark)
②.并發(fā)標(biāo)記(CMS concurrenr mark)
③.重新標(biāo)記(CMS remark)
④.并發(fā)清除(CMS concurrent sweep)
其中初始標(biāo)記、重新標(biāo)記這兩個(gè)步驟仍然需要停頓其他用戶線程(Stop The World)撬槽。初始標(biāo)記僅僅標(biāo)記出GC ROOTS能直接關(guān)聯(lián)到的對(duì)象聊训,速度很快,并發(fā)標(biāo)記階段是進(jìn)行GC ROOTS 根搜索算法階段恢氯,會(huì)判定對(duì)象是否存活带斑。而重新標(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)勋磕,但比并發(fā)標(biāo)記階段要短。
耗時(shí)最長(zhǎng)的是并發(fā)標(biāo)記與并發(fā)清除敢靡,是與用戶線程一起執(zhí)行的挂滓,所以整體來說,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)執(zhí)行的啸胧。
CMS收集器的優(yōu)點(diǎn):并發(fā)收集赶站、低停頓,但是CMS還遠(yuǎn)遠(yuǎn)達(dá)不到完美
三個(gè)缺點(diǎn)
CPU敏感
1.CMS收集器對(duì)CPU資源非常敏感纺念。在并發(fā)標(biāo)記贝椿、并發(fā)清除階段,雖然不會(huì)導(dǎo)致用戶線程停頓陷谱,但是會(huì)占用CPU資源而導(dǎo)致應(yīng)用程序變慢烙博,總吞吐量下降。CMS默認(rèn)啟動(dòng)的回收線程數(shù)是:(CPU數(shù)量+3) / 4烟逊。收集器線程所占用的CPU數(shù)量為:(CPU+3)/4=0.25+3/(4*CPU)渣窜。因此這時(shí)垃圾收集器始終不會(huì)占用少于25%的CPU,因此當(dāng)進(jìn)行并發(fā)階段時(shí)宪躯,雖然用戶線程可以跑乔宿,但是很緩慢,特別是雙核CPU的時(shí)候访雪,已經(jīng)占用了5/8的CPU详瑞,吞吐量會(huì)很低囤官。為了解決這種情況,產(chǎn)生了“增量式并發(fā)收集器”(Incremental Concurrent Mark Sweep/i-CMS)蛤虐。就是采用搶占方式來模擬多任務(wù)機(jī)制党饮,就是在并發(fā)(并發(fā)標(biāo)記、并發(fā)清除)階段驳庭,讓GC線程刑顺、用戶線程交替執(zhí)行,盡量減少GC線程獨(dú)占CPU饲常,這樣垃圾收集過程更長(zhǎng)蹲堂,但是對(duì)用戶程序影響小一些。實(shí)際上i-CMS效果很一般贝淤,目前已經(jīng)被聲明為“deprecated”柒竞。
2.CMS收集器無法處理浮動(dòng)垃圾,可能出現(xiàn)“Concurrent Mode Failure“播聪,失敗后而導(dǎo)致另一次Full GC的產(chǎn)生朽基。在并發(fā)清理階段用戶線程還在執(zhí)行,會(huì)產(chǎn)生新的垃圾离陶,這部分垃圾無法在這次回收中清理稼虎,而且用戶線程執(zhí)行,需要給用戶線程預(yù)留內(nèi)存以供執(zhí)行需要招刨,在默認(rèn)設(shè)置下霎俩,CMS收集器在老年代使用了68%的空間時(shí)就會(huì)被激活,也可以通過參數(shù)-XX:CMSInitiatingOccupancyFraction的值來提高觸發(fā)百分比沉眶,以降低內(nèi)存回收次數(shù)提高性能打却。
Concurrent Mode Failure解決方式
要是CMS運(yùn)行期間預(yù)留的內(nèi)存無法滿足程序其他線程需要,就會(huì)出現(xiàn)“Concurrent Mode Failure”失敗谎倔,這時(shí)候虛擬機(jī)將啟動(dòng)后備預(yù)案:臨時(shí)啟用Serial Old收集器來重新進(jìn)行老年代的垃圾收集柳击,這樣停頓時(shí)間就很長(zhǎng)了。所以說參數(shù)-XX:CMSInitiatingOccupancyFraction設(shè)置的過高將會(huì)很容易導(dǎo)致“Concurrent Mode Failure”失敗传藏,性能反而降低腻暮。
內(nèi)存碎片問題及解決
3.CMS是基于“標(biāo)記-清除”算法實(shí)現(xiàn)的收集器,為了解決存在很多內(nèi)存碎片導(dǎo)致內(nèi)存空間找不到連續(xù)的空間來分配不得不提前觸發(fā)一次Full GC毯侦。為了解決這個(gè)問題,CMS收集器提供了一個(gè)-XX:UseCMSCompactAtFullCollection開關(guān)參數(shù)具垫,用于在Full GC之后增加一個(gè)內(nèi)存碎片的合并整理過程侈离,但是內(nèi)存整理過程是無法并發(fā)的,因此解決了空間碎片問題筝蚕,卻使停頓時(shí)間變長(zhǎng)卦碾。還可通過-XX:CMSFullGCBeforeCompaction參數(shù)設(shè)置執(zhí)行多少次不壓縮的Full GC之后铺坞,跟著來一次碎片整理過程(默認(rèn)值是0,每次FullGC都進(jìn)行整理)洲胖。
G1收集器
G1(Garbage First)收集器是JDK1.7提供的一個(gè)新的面向服務(wù)端應(yīng)用的垃圾收集器济榨,其目標(biāo)就是替換掉JDK1.5發(fā)布的CMS收集器。
優(yōu)點(diǎn)
1.并發(fā)與并行:G1能充分利用多CPU绿映、多核環(huán)境下的硬件優(yōu)勢(shì)擒滑,使用多個(gè)CPU(CPU或CPU核心)來縮短停頓(Stop The World)時(shí)間。
2.分代收集:G1不需要與其他收集器配合就能獨(dú)立管理整個(gè)GC堆叉弦,但他能夠采用不同方式去處理新建對(duì)象和已經(jīng)存活了一段時(shí)間丐一、熬過多次GC的老年代對(duì)象以獲取更好收集效果。
3.空間整合:從整體來看是基于“標(biāo)記-整理”算法實(shí)現(xiàn)淹冰,從局部(兩個(gè)Region之間)來看是基于“復(fù)制”算法實(shí)現(xiàn)的库车,但是都意味著G1運(yùn)行期間不會(huì)產(chǎn)生內(nèi)存碎片空間,更健康樱拴,遇到大對(duì)象時(shí)柠衍,不會(huì)因?yàn)闆]有連續(xù)空間而進(jìn)行下一次GC,甚至一次Full GC晶乔。
4.可預(yù)測(cè)的停頓:降低停頓是G1和CMS共同關(guān)注點(diǎn)拧略,但G1除了追求低停頓,還能建立可預(yù)測(cè)的停頓模型瘪弓,可以明確地指定在一個(gè)長(zhǎng)度為M的時(shí)間片內(nèi)垫蛆,消耗在垃圾收集的時(shí)間不超過N毫秒
5.跨代特性:之前的收集器進(jìn)行收集的范圍都是整個(gè)新生代或老年代,而G1擴(kuò)展到整個(gè)Java堆(包括新生代腺怯,老年代)袱饭。
那么是怎么實(shí)現(xiàn)的呢?
如何實(shí)現(xiàn)新生代和老年代全范圍收集
其實(shí)它的Java堆布局就不同于其余收集器呛占,它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region)虑乖,仍然保留新生代和老年代的概念,可是不是物理隔離的晾虑,都是一部分Region(不需要連續(xù))的集合疹味。
如何建立可預(yù)測(cè)的停頓時(shí)間模型
是因?yàn)橛辛霜?dú)立區(qū)域Region的存在,就避免在Java堆中進(jìn)行全區(qū)域的垃圾收集帜篇,G1跟蹤各個(gè)Region里面的垃圾堆積的價(jià)值大胁谵唷(回收可以獲得的空間大小和回收所需要的時(shí)間的經(jīng)驗(yàn)值),后臺(tái)維護(hù)一個(gè)優(yōu)先隊(duì)列笙隙,根據(jù)每次允許的收集時(shí)間洪灯,優(yōu)先回收價(jià)值最大的Region(Garbage-First理念)。因此使用Region劃分內(nèi)存空間以及有優(yōu)先級(jí)的區(qū)域回收方式竟痰,保證了有限時(shí)間獲得盡可能高的收集效率签钩。
如何保證垃圾回收真的在Region區(qū)域進(jìn)行而不會(huì)擴(kuò)散到全局
由于Region并不是孤立的掏呼,一個(gè)Region的對(duì)象可以被整個(gè)Java堆的任意其余Region的對(duì)象所引用,在做可達(dá)性判定確定對(duì)象是否存活時(shí)铅檩,仍然會(huì)關(guān)聯(lián)到Java堆的任意對(duì)象憎夷,G1中這種情況特別明顯。而以前在別的分代收集里面昧旨,新生代規(guī)模要比老年代小許多拾给,新生代收集也頻繁得多,也會(huì)涉及到掃描新生代時(shí)也會(huì)掃描老年代的情況臼予,相反亦然鸣戴。解決:G1收集器Region之間的對(duì)象引用以及新生代和老年代之間的對(duì)象引用,虛擬機(jī)都是使用Remembered Set來避免全堆掃描粘拾。G1中每個(gè)Region都有一個(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之中(分代的例子中就檢查是否老年代對(duì)象引用了新生代的對(duì)象)缰雇,如果是則通過CardTable把相關(guān)引用信息記錄到被引用對(duì)象所屬的Region的Remembered Set之中入偷,當(dāng)進(jìn)行內(nèi)存回收時(shí),在GC根節(jié)點(diǎn)的枚舉范圍中加入Remembered Set即可避免全堆掃描械哟。
忽略Remembered Set的維護(hù)疏之,G1的運(yùn)行步驟可簡(jiǎn)單描述為:
①.初始標(biāo)記(Initial Marking)
②.并發(fā)標(biāo)記(Concurrenr Marking)
③.最終標(biāo)記(Final Marking)
④.篩選回收(Live Data Counting And Evacution)
1.初始標(biāo)記:初始標(biāo)記僅僅標(biāo)記GC Roots能直接關(guān)聯(lián)到的對(duì)象,并且修改TAMS(Next Top at Mark Start)的值暇咆,讓下一階段用戶程序并發(fā)運(yùn)行時(shí)锋爪,能在正確可用的Region中創(chuàng)建新的對(duì)象。這階段需要停頓線程爸业,不可并行執(zhí)行其骄,但是時(shí)間很短。
2.并發(fā)標(biāo)記:與CMS一致
3.最終標(biāo)記:此階段是為了修正在并發(fā)標(biāo)記期間因?yàn)橛脩艟€程繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一份標(biāo)記記錄扯旷,虛擬機(jī)將這段時(shí)間對(duì)象變化記錄在線程Remembered Set Logs里面拯爽,最終標(biāo)記階段需要把Remembered Set Logs的數(shù)據(jù)合并到Remembered Set中,這段時(shí)間需要停頓線程钧忽,但是可并行執(zhí)行毯炮。
4.篩選回收:對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序,根據(jù)用戶期望的GC停頓時(shí)間來制定回收計(jì)劃耸黑。
如果現(xiàn)有的垃圾收集器沒有出現(xiàn)任何問題桃煎,沒有任何理由去選擇G1,如果應(yīng)用追求低停頓崎坊,G1可選擇备禀,如果追求吞吐量,和Parallel Scavenge/Parallel Old組合相比G1并沒有特別的優(yōu)勢(shì)奈揍。