原地址:https://blog.csdn.net/clover_lily/article/details/80160726
本文主要介紹4種垃圾收集算法及8種垃圾收集器:
垃圾收集算法
1、標(biāo)記-清除算法(Mark-Sweep)
“標(biāo)記-清除”算法是最基礎(chǔ)的算法,分為“標(biāo)記”和“清除”兩個(gè)階段:首先標(biāo)記出所有需要回收的對(duì)象,在標(biāo)記完成后統(tǒng)一回收掉所有被標(biāo)記的對(duì)象。它主要由兩個(gè)缺點(diǎn):一個(gè)是效率問(wèn)題,標(biāo)記和清除過(guò)程的效率都不高;另一個(gè)是空間問(wèn)題,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會(huì)導(dǎo)致當(dāng)程序在以后的運(yùn)行過(guò)程中需要分配較大對(duì)象時(shí)無(wú)法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動(dòng)作。
2、復(fù)制算法(Copying)(針對(duì)新生代)
? ? ? 為了解決標(biāo)記清除算法的效率問(wèn)題,出現(xiàn)了復(fù)制算法,它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次使用其中的一塊。當(dāng)這塊的內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另一塊上面,然后再把已使用過(guò)的內(nèi)存空間一次清理掉。優(yōu)點(diǎn)是每次都是對(duì)其中的一塊進(jìn)行內(nèi)存回收,內(nèi)存分配時(shí)就不用考慮內(nèi)存碎片等復(fù)雜情況,只要移動(dòng)堆頂指針,按順序分配內(nèi)存即可,實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效。缺點(diǎn)是將內(nèi)存縮小為原來(lái)的一半,代價(jià)太高了一點(diǎn)。
????????現(xiàn)在的商業(yè)虛擬機(jī)都采用復(fù)制收集算法來(lái)回收新生代,有研究表明,新生代中的對(duì)象98%是朝生夕死的,所以并不需要按照1:1的比例來(lái)劃分內(nèi)存空間,而是將內(nèi)存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中的一塊Survivor。當(dāng)回收時(shí),將Eden和Survivor中還存活著的對(duì)象一次性地拷貝到另外一塊Survivor空間上,最后清理掉Eden和剛才用過(guò)的Survivor空間。HotSpot虛擬機(jī)默認(rèn)Eden和Survivor的大小比例是8:1,也就是每次新生代中可用內(nèi)存空間為整個(gè)新生代容量的90%(80%+10%),只有10%的內(nèi)存是會(huì)被“浪費(fèi)”的。當(dāng)然,并不能保證每次回收都只有10%的對(duì)象存活,當(dāng)Survivor空間不夠用時(shí),需要依賴其他內(nèi)存(這里指老年代)進(jìn)行分配擔(dān)保(Handle Promotion)。即如果另外一塊Survivor空間沒(méi)有足夠的空間存放上一次新生代收集下來(lái)的存活對(duì)象,這些對(duì)象將直接通過(guò)分配擔(dān)保機(jī)制進(jìn)入老年代。
3、標(biāo)記-整理算法(Mark-Compact)(針對(duì)老年代)
? ? ? 復(fù)制收集算法在對(duì)象存活率較高時(shí)就需要執(zhí)行較多的復(fù)制操作,效率將會(huì)變低。更關(guān)鍵的是,如果不想浪費(fèi)50%的空間,就需要有額外的空間進(jìn)行分配擔(dān)保,以應(yīng)對(duì)被使用的內(nèi)存中所有對(duì)象都100%存活的極端情況,所以在老年代一般不能直接選用復(fù)制收集算法。
? ? ?根據(jù)老年代的特點(diǎn)提出了“標(biāo)記-整理”算法,標(biāo)記過(guò)程仍然與“標(biāo)記-清除”算法一樣,但后續(xù)步驟不是直接對(duì)可回收對(duì)象進(jìn)行清理,而是讓所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉端邊界以外的內(nèi)存。
?標(biāo)記-整理的步驟:
標(biāo)記階段
整理階段:移動(dòng)存活對(duì)象,同時(shí)更新存活對(duì)象中所有指向被移動(dòng)對(duì)象的指針
整理的順序
?? ?不同算法中,堆遍歷的次數(shù),整理的順序,對(duì)象的遷移方式都有所不同。而整理順序又會(huì)影響到程序的局部性。主要有以下3種順序:?
?? ? 1. 任意順序:對(duì)象的移動(dòng)方式和它們初始的對(duì)象排列及引用關(guān)系無(wú)關(guān)?
?? ? ? 任意順序整理實(shí)現(xiàn)簡(jiǎn)單,且執(zhí)行速度快,但任意順序可能會(huì)將原本相鄰的對(duì)象打亂到不同的高速緩存行或者是虛擬內(nèi)存頁(yè)中,會(huì)降低賦值器的局部性。任意順序算法只能處理單一大小的對(duì)象,或者針對(duì)大小不同的對(duì)象需要分批處理;
?? ? 2. 線性順序:將具有關(guān)聯(lián)關(guān)系的對(duì)象排列在一起?
?? ? 3. 滑動(dòng)順序:將對(duì)象“滑動(dòng)”到堆的一端,從而“擠出”垃圾,可以保持對(duì)象在堆中原有的順序?
?? ?所有現(xiàn)代的標(biāo)記-整理回收器均使用滑動(dòng)整理,它不會(huì)改變對(duì)象的相對(duì)順序,也就不會(huì)影響賦值器的空間局部性。復(fù)制式回收器甚至可以通過(guò)改變對(duì)象布局的方式,將對(duì)象與其父節(jié)點(diǎn)或者兄弟節(jié)點(diǎn)排列的更近以提高賦值器的空間局部性。???
整理算法的限制,如整理過(guò)程需要2次或者3次遍歷堆空間;對(duì)象頭部可能需要一個(gè)額外的槽來(lái)保存遷移的信息。
部分整理算法:
雙指針回收算法:實(shí)現(xiàn)簡(jiǎn)單且速度快,但會(huì)打亂對(duì)象的原有布局
Lisp2算法(滑動(dòng)回收算法):需要在對(duì)象頭用一個(gè)額外的槽來(lái)保存遷移完的地址
引線整理算法:可以在不引入額外空間開銷的情況下實(shí)現(xiàn)滑動(dòng)整理,但需要2次遍歷堆,且遍歷成本較高
單次遍歷算法:滑動(dòng)回收,實(shí)時(shí)計(jì)算出對(duì)象的轉(zhuǎn)發(fā)地址而不需要額外的開銷
4、分代收集算法(Generational Collection)
? ? ? 當(dāng)前商業(yè)虛擬機(jī)的垃圾收集都采用“分代收集”算法,這種算法并無(wú)新的方法,只是根據(jù)對(duì)象的存活周期的不同將內(nèi)存劃分為幾塊,一般是把Java堆分為新生代和老年代,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴āT谛律?,每次垃圾收集時(shí)都發(fā)現(xiàn)有大批對(duì)象死去,只有少量存活,那就選用復(fù)制算法,只需要付出少量存活對(duì)象的復(fù)制成本就可以完成收集。而老年代中因?yàn)閷?duì)象存活率高、沒(méi)有額外空間對(duì)它進(jìn)行分配擔(dān)保,就必須使用“標(biāo)記-清理”或“標(biāo)記-整理”算法來(lái)進(jìn)行回收。
垃圾收集器
如果說(shuō)收集算法是內(nèi)存回收的方法論,垃圾收集器就是內(nèi)存回收的具體實(shí)現(xiàn)。下圖展示了7種不同分代的收集器,如果兩個(gè)收集器之間存在連線,就說(shuō)明他們可以搭配使用。并沒(méi)有最好的收集器這一說(shuō),我們需要選擇的是對(duì)具體應(yīng)用最合適的收集器。

1、Serial收集器(用于新生代)
單線程,在進(jìn)行垃圾收集時(shí)必須暫停其他所有的工作線程("Stop the World")。虛擬機(jī)運(yùn)行在Client模式下的默認(rèn)新生代收集器。簡(jiǎn)單而高效(與其他收集器的單線程比),對(duì)于限定單個(gè)CPU的環(huán)境來(lái)說(shuō),Serial收集器由于沒(méi)有線程交互的開銷,專心做垃圾收集自然可以獲得最高的單線程效率。
2、ParNew收集器(新生代)
ParNew收集器其實(shí)是Serial收集器的多線程版本,它是許多運(yùn)行在Server模式下的虛擬機(jī)中首選的新生代收集器,因?yàn)槌薙erial收集器外,目前只有它能與CMS收集器配合工作。
3、Parallel Scavenge收集器(“吞吐量?jī)?yōu)先”收集器)(新生代)
????????使用復(fù)制算法,并行多線程,這些特點(diǎn)與ParNew一樣,它的獨(dú)特之處是它的關(guān)注點(diǎn)與其他收集器不同,CMS等收集器的關(guān)注點(diǎn)是盡可能縮短垃圾收集時(shí)用戶線程的停頓時(shí)間,而Parallel Scavenge收集器的目的則是達(dá)到一個(gè)可控制的吞吐量(Throughput),即CPU用于運(yùn)行用戶代碼的時(shí)間與CPU總消耗時(shí)間的比值,吞吐量=運(yùn)行用戶代碼時(shí)間 /(運(yùn)行用戶代碼時(shí)間+垃圾收集時(shí)間),虛擬機(jī)總共運(yùn)行了100分鐘,其中垃圾收集花掉1分鐘,吞吐量就是99%。
???????停頓時(shí)間越短對(duì)于需要與用戶交互的程序來(lái)說(shuō)越好,良好的響應(yīng)速度能提升用戶的體驗(yàn);
????? ?高吞吐量可以最高效率地利用CPU時(shí)間,盡快地完成程序的運(yùn)算任務(wù),主要適合在后臺(tái)運(yùn)算而不太需要太多交互的任務(wù)。
參數(shù)設(shè)置:
-XX:MaxGCPauseMillis 控制最大垃圾收集停頓時(shí)間。(大于0的毫秒數(shù))停頓時(shí)間縮短是以犧牲吞吐量和新生代空間換取的。(新生代調(diào)的小,吞吐量跟著小,垃圾收集時(shí)間就短,停頓就?。?。
-XX:GCTimeRatio 直接設(shè)置吞吐量大小,0<x<100 的整數(shù),允許的最大GC時(shí)間=1/(1+x)。
-XX:+UseAdaptiveSizePolicy ?一個(gè)開關(guān)參數(shù),開啟GC自適應(yīng)調(diào)節(jié)策略(GC Ergonomics),將內(nèi)存管理的調(diào)優(yōu)任務(wù)(新生代大小-Xmn、Eden與Survivor區(qū)的比例-XX:SurvivorRatio、晉升老年代對(duì)象年齡-XX: PretenureSizeThreshold 、等細(xì)節(jié)參數(shù))交給虛擬機(jī)完成。這是Parallel Scavenge收集器與ParNew收集器的一個(gè)重要區(qū)別,另一個(gè)是吞吐量。
4、Serial Old收集器(老年代)
它是Serial收集器的老年代版本,單線程,使用“標(biāo)記-整理”算法。主要意義是被Client模式下的虛擬機(jī)使用。如果在Server模式下,它還有兩大用途:在JDK1.5及之前的版本中與Parallel Scavenge收集器搭配使用;作為CMS 收集器的后備預(yù)案,在并發(fā)收集發(fā)生Concurrent Mode Failure的時(shí)候使用。運(yùn)行過(guò)程同Serial收集器。
5、Parallel Old收集器(老年代)
它是Parallel Scavenge收集器的老年代版本,多線程,使用“標(biāo)記-整理”算法。在注重吞吐量及CPU資源敏感的場(chǎng)合,都可以優(yōu)先考慮Parallel Scavenge+Parallel Old收集器。工作過(guò)程如下:
6、CMS收集器(Concurrent Mark Sweep)
它是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器。優(yōu)點(diǎn):并發(fā)收集,低停頓?;凇皹?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),CMS收集器就非常符合這類應(yīng)用的需求。運(yùn)作過(guò)程較復(fù)雜,分為4個(gè)步驟:
初始標(biāo)記(CMS initial mark):需要“Stop The World”,標(biāo)記GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度快。
并發(fā)標(biāo)記(CMS concurrent mark):進(jìn)行GC Roots Tracing 過(guò)程
重新標(biāo)記(CMS remark):需要“Stop The World”,修正并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄。停頓時(shí)間:初始標(biāo)記<重新標(biāo)記<<并發(fā)標(biāo)記
并發(fā)清除(CMS concurrent sweep):時(shí)間較長(zhǎng)。
缺點(diǎn):
對(duì)CPU資源非常敏感,面向并發(fā)設(shè)計(jì)的程序都會(huì)對(duì)CPU資源較敏感。CMS默認(rèn)的回收線程數(shù):?(CPU數(shù)量+3)/4
無(wú)法處理浮動(dòng)垃圾,可能出現(xiàn)“Concurrent Mode Failure”失敗而導(dǎo)致另一次Full GC的產(chǎn)生。并發(fā)清理階段用戶程序運(yùn)行產(chǎn)生的垃圾過(guò)了標(biāo)記階段所以無(wú)法在本次收集中清理掉,稱為浮動(dòng)垃圾。CMS收集器默認(rèn)在老年代使用了68%的空間后被激活。若老年代增長(zhǎng)的不是很快,可以適當(dāng)調(diào)高參數(shù)-XX:CMSInitiatingOccupancyFraction? 提高觸發(fā)百分比,但調(diào)得太高會(huì)容易導(dǎo)致“Concurrent Mode Failure”失敗。
基于“標(biāo)記-清除”算法會(huì)產(chǎn)生大量空間碎片。提供開關(guān)參數(shù)-XX:+UseCMSCompactAtFullCollection?用于在“ 享受”完Full GC服務(wù)之后進(jìn)行碎片整理過(guò)程,內(nèi)存整理的過(guò)程是無(wú)法并發(fā)的。但是停頓時(shí)間會(huì)變長(zhǎng)。
-XX:CMSFullGCsBeforeCompation 設(shè)置在執(zhí)行多少次不壓縮的Full GC后,跟來(lái)來(lái)一次帶壓縮的。
7、G1收集器(Garbage First)
它是當(dāng)前收集器技術(shù)發(fā)展的最前沿成果。與CMS相比有兩個(gè)顯著改進(jìn):
基于“標(biāo)記-整理”算法實(shí)現(xiàn)收集器
非常精確地控制停頓
G1收集器可以在幾乎不犧牲吞吐量的前提下完成低停頓的內(nèi)存回收,這是由于它能夠極力避免全區(qū)域的垃圾收集,之前的收集器進(jìn)行收集的范圍都是整個(gè)新生代或老年代,而G1將整個(gè)Java堆(包括新生代、老年代)劃分為多個(gè)大小固定的獨(dú)立區(qū)域(Region),并且跟蹤這些區(qū)域里面的垃圾堆積程度,在后臺(tái)維護(hù)一個(gè)優(yōu)先列表,每次根據(jù)允許的收集時(shí)間,優(yōu)先回收垃圾最多的區(qū)域(這就是Garbage First名稱的由來(lái))。區(qū)域劃分、有優(yōu)先級(jí)的區(qū)域回收,保證了G1收集器在有限的時(shí)間內(nèi)可以獲得最高的收集效率。
————————————————
版權(quán)聲明:本文為CSDN博主「Pikaqiu_li」的原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/clover_lily/java/article/details/80160726