GC算法 垃圾收集器

? ? GC回收也是jvm學(xué)習(xí)中非常重要的一環(huán),在棧中棧幀是棧的主要內(nèi)存結(jié)構(gòu),每一個棧幀在棧中占用的內(nèi)存基本都是確定的,隨著方法的調(diào)用結(jié)束,棧幀內(nèi)存將會被回收,隨著這整個線程的結(jié)束,棧的內(nèi)存也會隨之被回收,而程序計(jì)數(shù)器只是一塊很小的內(nèi)存,只是用存儲字節(jié)碼執(zhí)行的行號,它伴隨的持有它的線程銷毀而被回收,而在堆內(nèi)存和方法區(qū)中,GC回收就不是那么簡單的事情了

? ? GC回收主要判斷對象是否還“活著”,判斷對象是否還活著的主要方法由兩個:

引用計(jì)數(shù)算法:在jvm中每個對象都有一個引用計(jì)數(shù)器,每當(dāng)有地方引用了這個對象時(shí)就會給這個計(jì)數(shù)器加1,當(dāng)引用失效時(shí)計(jì)數(shù)器就會減1,當(dāng)計(jì)數(shù)器為0時(shí),GC就會判斷它死了然后將它被收回,這個算法雖然簡單而且執(zhí)行效率非常的高(可以做到幾乎不影響程序執(zhí)行),但是它也會有一個問題:它無法解決對象之間的相互引用,而導(dǎo)致內(nèi)存溢出


object1與object2兩引用對象的相互引用

可達(dá)性分析算法:可達(dá)性分析算法是從離散數(shù)學(xué)的圖論引入的,判斷對象的引用鏈?zhǔn)欠窨蛇_(dá),從而判斷對象是否可以被回收掉。把一個叫做GC Roots的點(diǎn)看做是對象的起始點(diǎn),從這個點(diǎn)出發(fā)向下搜索能夠與對象的相連的,就認(rèn)為它是可達(dá)的,如果不相連就是不可達(dá)的,我們可以把這個結(jié)構(gòu)看做是一個內(nèi)存圖,GC會對這個圖,按照從GC Roots往下搜索的規(guī)則進(jìn)行遍歷,查找不可達(dá)對象然后將他們的內(nèi)存清理。


object5,6,7將會被回收

哪些對象可以看做是GC Roots呢?

其實(shí)只要jvm中調(diào)用的方法中正在被引用的對象都可以作為GC Roots,這些對象主要如下所列:

1、虛擬機(jī)棧中引用的對象(棧中的本地變量表);2、方法區(qū)中變量引用的對象、類靜屬性態(tài)引用的對象;3、本地方法棧中的Native方法引用的對象;4、活躍線程中被引用的對象

這里提多次提到的引用,我們又可將其分類,按照強(qiáng)弱程度依次排列為:強(qiáng)引用、軟引用、弱引用、虛引用。

強(qiáng)引用:類似于當(dāng)一個對象被new出來后,并且該對象的引用在方法中被調(diào)用,這時(shí)該對象的引用存在于棧中,而對象的實(shí)際內(nèi)容存在于堆中,這個方法運(yùn)行完成后,就會退出方法棧,則引用對象的引用數(shù)為0,這個對象就會被回收。當(dāng)對象的引用還在棧中并且內(nèi)存不足時(shí),jvm寧愿拋出OutofMemoryError也不會去回收該引用對象

軟引用:如果對象是軟引用,內(nèi)存空間充足時(shí),垃圾回收器就不會回收它;如果內(nèi)存空間不足了,就會回收這些對象的內(nèi)存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。jdk1.2后,提供了SoftReference實(shí)現(xiàn)軟引用

軟引用實(shí)現(xiàn)

弱引用:弱引用最多只能生存到下次GC回收之前。弱引用的對象會在即將到來的垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)弱引用的對象,不管當(dāng)前內(nèi)存空間足夠與否,都會回收它的內(nèi)存。jdk1.2后,提供了WeakReference實(shí)現(xiàn)弱引用

弱引用實(shí)現(xiàn)

虛引用:虛引用不會對對象的生命周期造成影響,也無法通過虛引用獲取一個對象實(shí)例,簡直就形同虛設(shè)一樣的存在,為對象套上虛引用的目的就是為了跟蹤對象被GC回收時(shí)的活動。jdk1.2后,提供了PhantomReference實(shí)現(xiàn)弱引用,用法和上圖類似,就不放圖了。

對象的死緩之finalize()方法:被finalize()方法覆蓋的對象并且該對象沒有被執(zhí)行過finalize()方法,在GC進(jìn)行可達(dá)性分析后這些對象會被篩選出來,然后扔到一個F-Queue隊(duì)列中,這時(shí)對象可以進(jìn)行自我救贖才可以逃過一劫(與其他的引用連接上),然后GC將會對這個隊(duì)列進(jìn)行檢查(這個檢查就是去執(zhí)行finalize()方法),如果這個隊(duì)列中的對象還沒有與其他的引用連接上那就徹底涼涼了,對象將會被GC視為垃圾進(jìn)行回收

輸出的結(jié)果

講了那么多估計(jì)大家也看累了,不過這些只是開胃菜,接下來才是重點(diǎn)的內(nèi)容?。?/p>

垃圾收集算法:

標(biāo)記-清除算法:這個算法比較容易理解,記住它的名字就能猜到它要干什么了,它大體分為了兩個步驟:1、標(biāo)記:標(biāo)記出所有需要進(jìn)行回收的對象。2、清除:清除掉被標(biāo)記的對象。這個算法雖然簡單粗暴,但是在垃圾回收的過程中是需要暫停其他的線程工作的,標(biāo)記和清除都得使用可達(dá)性分析算法,每次都需要從GC Root從上到下遍歷分別進(jìn)行標(biāo)記和清除,效率卻令人堪憂,兩個步驟的效率都很低下,而且它還會造成空間碎片過多,以致后期無法給較大的對象分配足夠的連續(xù)內(nèi)存


標(biāo)記-清除算法圖示

復(fù)制算法:復(fù)制算法解決了效率和空間碎片問題,它將可用內(nèi)存按容量按比例劃分為兩部分,一部分對象面,一部分空閑面,對象在對象面進(jìn)行創(chuàng)建,當(dāng)對象面的內(nèi)存使用完后,就將還存活著的對象面復(fù)制到空閑面上,然后將已經(jīng)所用過的對象面進(jìn)行清理,這樣每次都是其中的一面進(jìn)行回收,不必考慮內(nèi)存碎片問題,只要移動堆頂指針,按順序分配內(nèi)存即可,這種算法適用于對象存活率低的場景,年輕代的回收用的就是復(fù)制算法,但是對于生存期長的對象則會導(dǎo)致內(nèi)存使用率低下,因?yàn)檫@種算法的代價(jià)是犧牲了一部分內(nèi)存去實(shí)現(xiàn)復(fù)制回收,所以這個方法不適合垃圾對象少的場景,例如:老年代。在堆內(nèi)存里表現(xiàn)為:eden、survivorTo、survivorFrom的復(fù)制與清除,每一次都有一個survivor空間是空閑面,所以缺點(diǎn)就是,內(nèi)存使用率低下、


復(fù)制算法圖示

復(fù)制算法的應(yīng)用:

標(biāo)記-整理算法:又名標(biāo)記-壓縮算法,由于復(fù)制算法對老年代不適用,所以提出了標(biāo)記-整理算法對應(yīng)老年代的回收,標(biāo)記-整理算和標(biāo)記-清理算法類似,聽到名字就知道它想要干嘛,它是先標(biāo)記需要清除的對象,然后將存活對象進(jìn)行整理即將存活對象一塊一塊的都往一端移動,然后直接清理掉邊界以外的內(nèi)存

標(biāo)記-整理算法圖示

分代收集算法:這個算法是jvm主要使用的收集算法,jvm根據(jù)對象的生存周期不同的特點(diǎn)將對象分為了新生代和老年代,而各個收集算法對于新生代和老年代會產(chǎn)生效率或內(nèi)存使用率不一的結(jié)果,而根據(jù)對象生存周期的特點(diǎn)采取適合的算法就是分代收集算法的主要作用,在老年代中使用標(biāo)記-整理法,在新生代中使用復(fù)制算法

垃圾收集器:

在學(xué)習(xí)垃圾收集器前我們先了解jvm運(yùn)行模式:

Server:因?yàn)榇四J降奶摂M機(jī)是采用的重量級的虛擬機(jī),所以啟動速度慢,啟動完成進(jìn)入穩(wěn)定期后程序速度會比Client模式的快

Client:因?yàn)榇四J降奶摂M機(jī)為輕量級的虛擬機(jī),所以啟動速度快,啟動完成進(jìn)入穩(wěn)定期后程序速度會比Server模式的慢

查看我們jvm是哪種模式的可以執(zhí)行java -version 進(jìn)行查看:

新生代收集器:

? ? Serial收集器:中文名是串行收集器,使用的是復(fù)制算法(新生代采用復(fù)制算法,老生代采用標(biāo)志整理算法),通過設(shè)置-XX:+UseSerialGC來使用此收集器,這個一個歷史悠久并且最穩(wěn)定高效簡單的收集器,但是由于這個收集器是一個單線程的收集器,它工作時(shí)其他的線程的工作就必須得停頓( “Stop The World” :將用戶正常工作的線程全部暫停掉),雖然serial有諸多缺點(diǎn),但是它仍然是虛擬機(jī)運(yùn)行于Client模式下新生代的默認(rèn)收集器(很神奇吧!),原因是Serial收集器簡單而高效(與其他收集器的單線程比),對于限定單個CPU的環(huán)境來說,沒有線程交互的開銷,專心做GC,自然可以獲得最高的單線程效率

? ? ParNew收集器(Serial收集器的多線程版本-使用多條線程進(jìn)行GC):這是Serial收集器的多線程版本,也是新生代收集器,通過設(shè)置-XX:+UseParNewGC來使用此收集器,使用的是復(fù)制算法,其特點(diǎn)和Serial完全一樣,只是除了多線程收集之外,與Serial相比沒有其他創(chuàng)新的地方,它是許多運(yùn)行在Server模式下虛擬機(jī)的首選新生代收集器,除了Serial收集器意外,只有它能夠與老年代收集器CMS配合工作,由于ParNew有線程交互開銷,所它的單核收集效率比Serial收集器低

? ? Parallel Scavenge收集器:這是一個新生代收集器,是多線程收集器,使用的是復(fù)制算法的,通過設(shè)置-XX:+UseParallelGC來使用此收集器。這個收集器更加關(guān)注系統(tǒng)的吞吐量(吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+垃圾收集時(shí)間),CMS垃圾收集器的關(guān)注點(diǎn)更多的是用戶線程的停頓時(shí)間(提高用戶體驗(yàn))。通過參數(shù)打開自適應(yīng)調(diào)節(jié)策略。虛擬機(jī)會根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息,動態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí)間或者最大吞吐量,這個叫做GC自適應(yīng)的調(diào)節(jié)策略,使用Parallel Scavenge收集器可以配合虛擬機(jī)的自適應(yīng)調(diào)節(jié)策略,這個收集器是運(yùn)行在Server模式下的新生代默認(rèn)收集器

老年代收集器:

? ? Serial Old收集器:Serial收集器的老年代版本,使用的是標(biāo)記-整理算法,通過設(shè)置-XX:+UseSerialOldGC來使用此收集器。這個收集器的特點(diǎn)和Serial幾乎是一樣的:單線程、工作時(shí)其他線程必須暫停工作、穩(wěn)定簡單高效、Client模式下老年代的默認(rèn)收集器,它主要有兩大用途:一種用途是在JDK1.5以及以前的版本中與Parallel Scavenge收集器搭配使用,另一種用途是作為CMS收集器的后備方案

????Parallel Old收集器:Parallel Scavenge收集器的老年代版本,JDK1.6及之后用來代替老年代的Serial Old收集器,使用多線程和“標(biāo)記-整理”算法。通過設(shè)置-XX:+UseParallelOldGC來使用此收集器。在Server模式,多CPU的情況下,如果注重吞吐量以及CPU資源的場合,都可以優(yōu)先考慮 Parallel Scavenge收集器和Parallel Old收集器。

? ? CMS收集器:CMS收集器是老年代收集器,使用的是標(biāo)記-清除算法,它以獲取最短回收停頓時(shí)間為目標(biāo)的收集器,通過設(shè)置-XX:+UseConcMarkSweepGC來使用此收集器。目前很大一部分的java應(yīng)用都集中在互聯(lián)網(wǎng)或者B/S系統(tǒng)的服務(wù)器上,這類應(yīng)用尤其重視響應(yīng)速度,盡量縮短系統(tǒng)的停頓時(shí)間,以給用戶帶來較好的體驗(yàn),它的工作原理是基于標(biāo)記-清除算法實(shí)現(xiàn)的,整個過程是分為了初始標(biāo)記,并發(fā)標(biāo)記,重新標(biāo)記,并發(fā)清除四個步驟

? ??應(yīng)用場景:

????????1、與用戶交互較多的場景;(如常見WEB、B/S-瀏覽器/服務(wù)器模式系統(tǒng)的服務(wù)器上的應(yīng)用)

????????2、希望系統(tǒng)停頓時(shí)間最短,注重服務(wù)的響應(yīng)速度;

? ??CMS的缺點(diǎn):由于CMS是基于“標(biāo)記+清除”算法來回收老年代對象的,因此長時(shí)間運(yùn)行后會產(chǎn)生大量的空間碎片問題,可能導(dǎo)致新生代對象晉升到老生代失敗。由于碎片過多,將會給大對象的分配帶來麻煩。因此會出現(xiàn)這樣的情況,老年代還有很多剩余的空間,但是找不到連續(xù)的空間來分配當(dāng)前對象,這樣不得不提前觸發(fā)一次Full GC。解決辦法是使用"-XX:+UseCMSCompactAtFullCollection"和" -XX:+CMSFullGCsBeforeCompaction",需要結(jié)合使用。CMS收集器提供?XX:+UseCMSCompactAlFullCollection標(biāo)志,使得CMS出現(xiàn)上面這種情況時(shí)不進(jìn)行Full GC,而開啟內(nèi)存碎片的合并整理過程;

究極收集器:

? G1收集器:G1的使命是在未來替換CMS,并且在JDK1.9已經(jīng)成為默認(rèn)的收集器,它可以做到并發(fā)并行(可以與用戶線程并發(fā)執(zhí)行以縮短系統(tǒng)停頓時(shí)間),它的使命就是替代jdk1.5中發(fā)布的CMS收集器,相比CMS收集器有以下特點(diǎn):?

1、并行與并發(fā):G1能充分利用CPU、多核環(huán)境下的硬件優(yōu)勢,使用多個CPU(CPU或者CPU核心)來縮短stop-The-World停頓時(shí)間。部分其他收集器原本需要停頓Java線程執(zhí)行的GC動作,G1收集器仍然可以通過并發(fā)的方式讓java程序繼續(xù)執(zhí)行。

2、空間整合G1收集器采用標(biāo)記整理算法,不會產(chǎn)生內(nèi)存空間碎片。分配大對象時(shí)不會因?yàn)檎也坏阶銐虼蟮倪B續(xù)內(nèi)存空間而觸發(fā)下一次GC

3、可預(yù)測停頓:降低停頓時(shí)間是CMS和G1共同的關(guān)注點(diǎn),但是G1除了追求低停頓外,還能建立可預(yù)測的停頓時(shí)間模型,能讓使用者明確指定在一個長度為N毫秒時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不得超過N毫秒。

4、分代收集:在G1收集器中的內(nèi)存物理結(jié)構(gòu)已經(jīng)不再是是jdk8時(shí)把新生代和老年代分別全部放在一大塊內(nèi)存內(nèi)存中,而是把內(nèi)存分成了一小塊一塊的Region,每塊內(nèi)存最大32M,整個堆內(nèi)存最多有2048個Region,也就是最大堆內(nèi)存是60G到70G,但是在概念上依然是保留了分代,收集垃圾也使用了分代收集的方式,清除垃圾的方式類似復(fù)制算法,但是比復(fù)制算法復(fù)制很多

G1的內(nèi)存模型

上面提到的收集器的收集范圍是整個新生代或者老年代,而G1不再是這樣。G1收集器將java堆內(nèi)存分為了多個大小相等的獨(dú)立區(qū)域,雖然還保留有新生代和老年代的概念,但是新生代和老年代不再是物理隔閡,它們都是其中的一部分(可以不連續(xù))。G1的新生代收集跟ParNew類似,當(dāng)新生代占用達(dá)到一定比例的時(shí)候,開始發(fā)出效果。和CMS類似,G1收集器收集老年代對象會有短暫停頓


G1如何找到引用對象?

前面的垃圾收集器都是使用了可達(dá)性算法找對象之間的引用,而由于G1的這種結(jié)構(gòu),各個Region全部在同一個區(qū)域,Region中的對象可能與其他的Region有引用關(guān)系,如何使用可達(dá)性算法找,是需要掃描整個堆區(qū)域的,這顯然和G1作為各種收集器的升級版身份不符合,所以G1引入了一個叫做Remember Set的集合幫它記錄Region的引用關(guān)系,每個Region都擁有自己的Remember Set,通過掃描Remember Set就能找到每個Region中對象的引用關(guān)系了

圖中的Rset就是?Remember Set

指定使用G1收集器:"-XX:+UseG1GC"

當(dāng)整個Java堆的占用率達(dá)到參數(shù)值時(shí),開始并發(fā)標(biāo)記階段,默認(rèn)為45:"-XX:InitiatingHeapOccupancyPercent"

為G1設(shè)置暫停時(shí)間目標(biāo),默認(rèn)值為200毫秒:"-XX:MaxGCPauseMillis"

設(shè)置每個Region大小,范圍1MB到32MB,目標(biāo)是在最小Java堆時(shí)可以擁有約2048個Region:"-XX:G1HeapRegionSize"

新生代最小值,默認(rèn)值5%:"-XX:G1NewSizePercent"

新生代最大值,默認(rèn)值60%:"-XX:G1MaxNewSizePercent"設(shè)置STW期間,并行GC線程數(shù):"-XX:ParallelGCThreads"

設(shè)置并發(fā)標(biāo)記階段,并行執(zhí)行的線程數(shù):"-XX:ConcGCThreads"


為什么只有ParNew能與CMS收集器配合

CMS是HotSpot在JDK1.5推出的第一款真正意義上的并發(fā)收集器,第一次實(shí)現(xiàn)了讓垃圾收集線程與用戶線程(基本上)同時(shí)工作,CMS作為老年代收集器,但卻無法與JDK1.4已經(jīng)存在的新生代收集器Parallel Scavenge配合工作,因?yàn)镻arallel Scavenge(以及G1)都沒有使用傳統(tǒng)的GC收集器代碼框架,而另外獨(dú)立實(shí)現(xiàn),而其余幾種收集器則共用了部分的框架代碼;


下圖是各個垃圾收集器能相互配合的選擇了相應(yīng)的老年代收集器,

例如:選擇serial系統(tǒng)自動激活serial old, 選擇parNew自動激活CMS,選擇parallel scavenge自動激活parallel? old

只有兩個回收器之間有連線才能配合使用


jdk1.7 默認(rèn)垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.8 默認(rèn)垃圾收集器Parallel Scavenge(新生代)+Parallel Old(老年代)

jdk1.9 默認(rèn)垃圾收集器G1

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容