java 2分代复制垃圾回收_Java对象的后事处理——垃圾回收(二)
1 先談Finalize()
finalize()能做的所有工作,使用try-finally或者其他方式都可以做得更好、更及時(shí),所以筆者建議大家完全可以忘掉Java語(yǔ)言中有這個(gè)方法的存在。
——《深入理解JVM》
finalize()方法確實(shí)可以實(shí)現(xiàn)一次對(duì)象的自救,但是其不確定性和昂貴的運(yùn)行代價(jià)都表明這個(gè)方法的使用需要十分的慎重。那么finalize()在什么時(shí)期起作用又是如何實(shí)現(xiàn)對(duì)象的自救的呢?首先我們要理解虛擬機(jī)在掃描到死亡對(duì)象的時(shí)候并不是直接回收的,而是進(jìn)行一次標(biāo)記并且篩選,篩選的條件就是其對(duì)象的finalize方法是否有必要執(zhí)行。如果當(dāng)前對(duì)象沒有重寫finalize方法或者已經(jīng)調(diào)用過(guò)一次finalize方法,那么則視為沒有必要執(zhí)行,此時(shí)便失去自救的機(jī)會(huì),放入"即將回收"集合中。
否則的話,則將對(duì)象放入一個(gè)叫F-Queue的隊(duì)列中,稍后虛擬機(jī)將一個(gè)個(gè)的執(zhí)行隊(duì)列中對(duì)象的finalize方法(就是在此處對(duì)象可以在finalize方法中將自身關(guān)聯(lián)到引用鏈,從而暫時(shí)逃脫被回收的命運(yùn)),需要注意的是虛擬機(jī)保證執(zhí)行但不保證執(zhí)行完finalize方法,原因是如果finalize方法執(zhí)行時(shí)間過(guò)長(zhǎng)或者陷入死循環(huán),則可能讓系統(tǒng)奔潰。全部執(zhí)行之后,虛擬機(jī)將對(duì)隊(duì)列的對(duì)象重新標(biāo)記一次,如果還不在引用鏈中則GG,否則將其移出"即將回收"集合。下面例子參考《深入理解JVM》實(shí)現(xiàn)自救并且驗(yàn)證只能自救一次的過(guò)程。
public classTestForGc {/**定義一個(gè)根節(jié)點(diǎn)的靜態(tài)變量*/
public staticTestForGc INSTANCE;/*** 重寫finalize方法,讓其被標(biāo)記為有必要執(zhí)行并且加入F-Q
*
*@throwsThrowable*/@Overrideprotected void finalize() throwsThrowable {super.finalize();
System.err.println("finalize method in TestForGc Class invoked!");//將自身關(guān)聯(lián)到根節(jié)點(diǎn)中,實(shí)現(xiàn)自救
INSTANCE = this;
}public static void main(String[] args) throwsInterruptedException {
INSTANCE= newTestForGc();
INSTANCE= null;
System.gc();//睡眠1S,保證F-Q中的方法執(zhí)行完畢
TimeUnit.SECONDS.sleep(1);if(Objects.nonNull(INSTANCE)) {
System.out.println("i successfully save myself by finalize method!");
}else{
System.out.println("i am dead :(");
}/** 下面驗(yàn)證finalize方法只能調(diào)用一次
* 幾乎完全一樣的代碼,卻是不同的結(jié)局*/INSTANCE= null;
System.gc();//睡眠1S
TimeUnit.SECONDS.sleep(1);if(Objects.nonNull(INSTANCE)) {
System.out.println("i successfully save myself by finalize method again!");
}else{
System.out.println("couldn't invoke finalize again, i am dead :(");
}
}
}
執(zhí)行結(jié)果:
2 垃圾回收器
如果說(shuō)回收算法是接口,那么垃圾回收器就是這些接口的實(shí)現(xiàn)類,共有7種回收器,接下來(lái)一一羅列。
2.1 Serial垃圾回收器
Serial是一種單線程垃圾回收器,在工作的時(shí)候的時(shí)候會(huì)暫停所有的用戶線程,也就是"stop-the-world",雖然單線程代表了用戶線程的停頓,但是也意味著其不用進(jìn)行線程的交互從而有更高的收集 效率。Serial采用復(fù)制算法,是Client端新生代的默認(rèn)垃圾回收器。其工作圖類似于:
2.2 ParNew垃圾回收器。
ParNew是Serial回收器的多線程版本,是Server端新生代的默認(rèn)回收器,除了并行多線程之外,其他包括實(shí)現(xiàn)都是一模一樣,當(dāng)然也是采用復(fù)制算法。還有一點(diǎn)重要的是,新生代的收集器除了Serial之外,只有ParNew能跟年老代的CMS合作,其在低CPU的情況下效率比Serial低,但是在多個(gè)CPU的情況下要好的多。其工作圖:
2.3 Parallel Scavenge垃圾回收器
跟ParNew類似,作用于新生代,并行多線程并且也是采用復(fù)制算法。但是其關(guān)注的點(diǎn)卻不同,其著重的是一種叫做"吞吐量"的東西。所謂的"吞吐量"=運(yùn)行用戶代碼的時(shí)間 / (運(yùn)行用戶代碼的時(shí)間 + GC時(shí)間),也就是說(shuō)其更加注重用戶代碼運(yùn)行時(shí)間而不是減少GC停頓時(shí)間。相對(duì)于其他收集器來(lái)說(shuō),可以更加高效的利用CPU,更加適合作為在后臺(tái)運(yùn)算而不大需要交互的任務(wù)。Parallel收集器提供了兩個(gè)比較重要的參數(shù)。
-XX:MaxGCPauseMillis:表示收集器將盡可能的在這個(gè)參數(shù)設(shè)定的毫秒數(shù)內(nèi)完成回收工作。但這并不代表其設(shè)置的越低越好,縮減回收時(shí)間是通過(guò)減少吞吐量換來(lái)的,如果設(shè)置得太低可能導(dǎo)致頻繁的GC。
-XX:GCTimeRatio:表示代碼運(yùn)行時(shí)間和垃圾回收時(shí)間的比率,比如說(shuō)設(shè)置為19,那么則垃圾回收時(shí)間占比為 1 / (1+19) = 5%,默認(rèn)是99。
2.4 Serial Old垃圾回收器
Serial的年老代版本,同Serial基本相似,不同的是采用的是標(biāo)記-整理算法實(shí)現(xiàn),作為Client端默認(rèn)的年老代收集器。如果在Server端的話,那么其主要作用有二:
1、跟新生代的Parallel Scavenge收集器配合。
2、做一個(gè)有價(jià)值的"備胎":當(dāng)CMS垃圾回收器因?yàn)轭A(yù)留空間問(wèn)題放不下對(duì)象而發(fā)生Concurrent Mode Fail時(shí),作為其備選方案執(zhí)行垃圾回收。
2.5 Parallel Old垃圾回收器
Parallel Scavenge的年老代版本,多線程并行,同樣注重吞吐量,使用標(biāo)記-整理算法。這個(gè)收集器可以跟新生代的Parallel Svavenge一起搭配使用,在注重吞吐量和CPU資源敏感的場(chǎng)合中是一對(duì)很好的組合。
2.6 CMS垃圾回收器
來(lái)了,它來(lái)了!CMS垃圾回收器被當(dāng)做是具有劃時(shí)代意義的、真正實(shí)現(xiàn)并發(fā)的垃圾回收器,總而言之=》
,--^----------,--------,-----,-------^--,
| ||||||||| `--------' | O
`+---------------------------^----------|
`\_,-------, _______________________強(qiáng)__|
/ XXXXXX /`| /
/ XXXXXX / `\ /
/ XXXXXX /\______(
/ XXXXXX /
/ XXXXXX /
(________(
`------'
CMS是一款并發(fā)的垃圾回收器,但并不代表全程都不需要停頓,只是大部分時(shí)間是跟用戶線程一起執(zhí)行的。其整個(gè)GC過(guò)程中總共有4個(gè)階段。
1、初始標(biāo)記:簡(jiǎn)單的標(biāo)記所有的根節(jié)點(diǎn),需要暫停所有的用戶線程,即"stop-the-world",耗時(shí)較短。關(guān)于GCRooots的過(guò)程可以看下另一篇文章——垃圾回收(一)。
2、并發(fā)標(biāo)記:跟用戶線程一起工作,尋找堆中的死亡對(duì)象,整個(gè)過(guò)程耗時(shí)最長(zhǎng)。
3、重新標(biāo)記:再次掃描,主要對(duì)象是并發(fā)標(biāo)記過(guò)程中又新增的對(duì)象,也就是驗(yàn)漏。多線程,需要STW,時(shí)間相對(duì)并發(fā)標(biāo)記來(lái)說(shuō)短。
4、并發(fā)清除:GC線程跟用戶線程一起執(zhí)行,清除標(biāo)記的死亡對(duì)象,"浮動(dòng)垃圾"在此階段產(chǎn)生。
然而,優(yōu)秀如CMS也會(huì)有不足之處,總共四個(gè)階段的標(biāo)記及清除算法的實(shí)現(xiàn)必定為其帶來(lái)一些使用的麻煩。
缺點(diǎn):
1、占用一定CPU資源:其有兩個(gè)階段需要并發(fā)跟用戶線程一起執(zhí)行,也就是說(shuō)要跟用戶線程搶占CPU的時(shí)間片,會(huì)占用一定的CPU資源,如果CPU資源不太優(yōu)質(zhì)的情況下,可能會(huì)造成不小的影響。
2、空間利用率不能達(dá)到最大:由于并發(fā)清除時(shí)用戶線程也在運(yùn)行,那么在GC結(jié)束前必定會(huì)產(chǎn)生一些額外的垃圾,那么就必須給這些垃圾預(yù)留一定的空間,否則會(huì)導(dǎo)致內(nèi)存不足從而報(bào)"Concurrent Mode Failure",此時(shí)虛擬機(jī)便啟用后備方案——使用Serial Old來(lái)進(jìn)行垃圾回收,進(jìn)而浪費(fèi)更多的時(shí)間。
3、內(nèi)存碎片導(dǎo)致提前FullGC:CMS采用的是標(biāo)記-清除算法,也就是說(shuō)會(huì)產(chǎn)生內(nèi)存碎片,那么可能出現(xiàn)大對(duì)象放不下的情況,進(jìn)而不得不提前進(jìn)行一次FullGC。為了解決這個(gè)問(wèn)題,虛擬機(jī)提供了兩個(gè)參數(shù)-XX:+UseCMSCompactAtFullCollection和-XX:CMSFullGCsBeforeCompaction,分別表示CMS頂不住要進(jìn)行FullGC的時(shí)候進(jìn)行內(nèi)存的整理(整理的過(guò)程中無(wú)法并發(fā),停頓時(shí)間不得不變長(zhǎng)) 和進(jìn)行多少次不壓縮的FullGC之后來(lái)一次整理的GC(默認(rèn)0次,表示每次都進(jìn)行內(nèi)存整理)。
2.7 G1垃圾回收器
G1是一個(gè)新秀垃圾回收器,被賦予了很大的使命——取代CMS。G1作為新時(shí)代的垃圾回收器,相對(duì)于其他垃圾回收器來(lái)說(shuō)有許多優(yōu)勢(shì)。
1、并行和并發(fā):G1可以利用現(xiàn)在的硬件優(yōu)勢(shì),縮短GC時(shí)stop-the-world的停頓時(shí)間,并且GC的時(shí)候同時(shí)也能讓用戶線程執(zhí)行。
2、分代收集:跟其他垃圾回收器不同,G1沒有物理上的年老代和新生代,其將內(nèi)存分成了多個(gè)獨(dú)立的Region,每個(gè)Region都可能表示屬于新生代還是年老代,所以不需要一堆Region湊放在一起然后將這塊區(qū)域稱作新生代,它們之間并不需要連續(xù),所以只有概念上的分代,也是這種分代方式使得G1可以獨(dú)立管理這個(gè)堆空間,不需要跟其他回收器合作。
3、空間整合:G1的算法從Region層面看屬于復(fù)制算法(從一個(gè)Region復(fù)制到另一個(gè)),但是從整體看又是標(biāo)記-整理法。然而不管是哪種,都表示G1不會(huì)產(chǎn)生內(nèi)存碎片,不會(huì)因?yàn)榭臻g不連續(xù)放不下大對(duì)象而出現(xiàn)FullGC的情況。
G1回收器將內(nèi)存空間分成若干個(gè)Region,并且這些Region之前相互獨(dú)立。但是我們都知道這并不能真正的獨(dú)立,因?yàn)橐粋€(gè)Region中的對(duì)象不一定只會(huì)被當(dāng)前Region的其他對(duì)象引用,而可能被堆中的其他對(duì)象引用,那G1是如何實(shí)現(xiàn)避免全堆掃描的呢?這個(gè)問(wèn)題在分代的其他回收器中也有,但是在這里突顯得更加明顯而已。再G1中,對(duì)象本身都會(huì)有一個(gè)Remembered Set,這個(gè)Set存放著當(dāng)前對(duì)象被其他區(qū)域?qū)ο笠玫男畔?#xff0c;這樣子,在掃描引用的時(shí)候加上這個(gè)Set就可以避免全堆掃描了。
具體實(shí)現(xiàn)大致為:虛擬機(jī)在發(fā)現(xiàn)程序正在進(jìn)行對(duì)Reference類型的寫操作時(shí),會(huì)暫時(shí)中斷寫操作,然后檢查Reference引用的對(duì)象是否處于不同的區(qū)域(如果是分代,則只對(duì)年老代的對(duì)象進(jìn)行檢查,檢查是否引用的對(duì)象在新生代),如果是的話則將引用信息記錄在被引用的Remembered Set中,這樣在GC的時(shí)候加上Remembered Set的掃描就可以避免全堆掃描了。
跟CMS類型,G1也有四個(gè)階段(不算Remembered Set的掃描),雖然相似但是還是有些區(qū)別的。
1、初始標(biāo)記:標(biāo)記可達(dá)的根節(jié)點(diǎn),STW,單線程,時(shí)間短。
2、并發(fā)標(biāo)記:跟用戶線程同時(shí)執(zhí)行,并發(fā)執(zhí)行時(shí)對(duì)象可能會(huì)產(chǎn)生引用變化,其會(huì)將這些變化記錄在Remembered Set Logs中,待下個(gè)階段整合。
3、最終標(biāo)記:驗(yàn)漏,將并發(fā)標(biāo)記階段的引用變化記錄Remembered Set Logs整合到Remembered Set中。
4、篩選回收:對(duì)各個(gè)Region中的回收價(jià)值進(jìn)行排序,然后執(zhí)行回收計(jì)劃。暫停用戶線程,并行執(zhí)行。
3 小結(jié)
本文首先介紹了“對(duì)象自救”的方法——finalize,并且用一個(gè)小例子演示了對(duì)象如何實(shí)現(xiàn)自救。接著介紹了7種不同的垃圾回收器,新生代中有單線程的Serial可以作為Client端新生代的默認(rèn)回收器,有多線程版本的Serial——ParNew,還有著重點(diǎn)不同(吞吐量)的Parallel Scavenge;年老代方面有單線程的Serial Old、跨時(shí)代意義的并發(fā)回收器——CMS,雖然優(yōu)秀但不可避免的有三個(gè)缺點(diǎn)、還有吞吐量年老代版本——Parallel Old收集器,最后還簡(jiǎn)單介紹了G1收集器的幾個(gè)過(guò)程還有獨(dú)立的Region間是如何實(shí)現(xiàn)避免堆掃描的。
整體下來(lái)整篇行文還有些粗糙,日后會(huì)慢慢的圓潤(rùn),如果有關(guān)于這方面好的文章可以在下面評(píng)論區(qū)分享學(xué)習(xí)一下,下方為各個(gè)垃圾回收器的搭配圖。
It helps me a lot if you could share your opinion with us.
總結(jié)
以上是生活随笔為你收集整理的java 2分代复制垃圾回收_Java对象的后事处理——垃圾回收(二)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Python学习笔记(15) 网络爬虫使
- 下一篇: JVM学习-深入理解Java虚拟机代码实