學(xué)習(xí)java的同學(xué)注意了?。?! 學(xué)習(xí)過程中遇到什么問題或者想獲取學(xué)習(xí)資源的話,歡迎加入Java學(xué)習(xí)交流群,群號(hào)碼:183993990 我們一起學(xué)Java!
垃圾收集算法主要有:標(biāo)記-清除、復(fù)制和標(biāo)記-整理。
1、標(biāo)記-清除算法
對(duì)待回收的對(duì)象進(jìn)行標(biāo)記。算法缺點(diǎn):效率問題,標(biāo)記和清除過程效率都很低;空間問題,收集之后會(huì)產(chǎn)生大量的內(nèi)存碎片,不利于大對(duì)象的分配。
2、復(fù)制算法
復(fù)制算法將可用內(nèi)存劃分成大小相等的兩塊A和B,每次只使用其中一塊,當(dāng)A的內(nèi)存用完了,就把存活的對(duì)象復(fù)制到B,并清空A的內(nèi)存,不僅提高了標(biāo)記的效率,因?yàn)橹恍枰獦?biāo)記存活的對(duì)象,同時(shí)也避免了內(nèi)存碎片的問題,代價(jià)是可用內(nèi)存縮小為原來的一半。
3、標(biāo)記-整理算法
在老年代中,對(duì)象存活率較高,復(fù)制算法的效率很低。在標(biāo)記-整理算法中,標(biāo)記出所有存活的對(duì)象,并移動(dòng)到一端,然后直接清理邊界以外的內(nèi)存。
在可達(dá)性分析過程中,為了準(zhǔn)確找出與GC Roots相關(guān)聯(lián)的對(duì)象,必須要求整個(gè)執(zhí)行引擎看起來像是被凍結(jié)在某個(gè)時(shí)間點(diǎn)上,即暫停所有運(yùn)行中的線程,不可以出現(xiàn)對(duì)象的引用關(guān)系還在不斷變化的情況。
如何快速枚舉GC Roots?
GC Roots主要在全局性的引用(常量或類靜態(tài)屬性)與執(zhí)行上下文(本地變量表中的引用)中,很多應(yīng)用僅僅方法區(qū)就上百兆,如果進(jìn)行遍歷查找,效率會(huì)非常低下。
在HotSpot中,使用一組稱為OopMap的數(shù)據(jù)結(jié)構(gòu)進(jìn)行實(shí)現(xiàn)。類加載完成時(shí),HotSpot把對(duì)象內(nèi)什么偏移量上是什么類型的數(shù)據(jù)計(jì)算出來存儲(chǔ)到OopMap中,通過JIT編譯出來的本地代碼,也會(huì)記錄下棧和寄存器中哪些位置是引用。GC發(fā)生時(shí),通過掃描OopMap的數(shù)據(jù)就可以快速標(biāo)識(shí)出存活的對(duì)象。
如何安全的GC?
線程運(yùn)行時(shí),只有在到達(dá)安全點(diǎn)(Safe Point)才能停頓下來進(jìn)行GC。
基于OopMap數(shù)據(jù)結(jié)構(gòu),HotSpot可以快速完成GC Roots的遍歷,不過HotSpot并不會(huì)為每條指令都生成對(duì)應(yīng)的OopMap,只會(huì)在Safe Point處記錄這些信息。
所以Safe Point的選擇很重要,如果太少可能導(dǎo)致GC等待的時(shí)間太長(zhǎng),如果太頻繁可能導(dǎo)致運(yùn)行時(shí)的性能問題。大部分指令的執(zhí)行時(shí)間都非常短暫,通常會(huì)選擇一些執(zhí)行時(shí)間較長(zhǎng)的指令作為Safe Point,如方法調(diào)用、循環(huán)跳轉(zhuǎn)和異常跳轉(zhuǎn)等。
關(guān)于Safe Point更多的信息,可以看看這篇文章 JVM的Stop The World,安全點(diǎn),黑暗的地底世界
發(fā)生GC時(shí),如何讓所有線程跑到最近的Safe Point再暫停?
當(dāng)發(fā)生GC時(shí),不直接對(duì)線程進(jìn)行中斷操作,而是簡(jiǎn)單的設(shè)置一個(gè)中斷標(biāo)志,每個(gè)線程運(yùn)行到Safe Point的時(shí)候,主動(dòng)去輪詢這個(gè)中斷標(biāo)志,如果中斷標(biāo)志為真,則將自己進(jìn)行中斷掛起。
這里忽略了一個(gè)問題,當(dāng)發(fā)生GC時(shí),運(yùn)行中的線程可以跑到Safe Point后進(jìn)行掛起,而那些處于Sleep或Blocked狀態(tài)的線程在此時(shí)無(wú)法響應(yīng)JVM的中斷請(qǐng)求,無(wú)法到Safe Point處進(jìn)行掛起,針對(duì)這種情況,可以使用安全區(qū)域(Safe Region)進(jìn)行解決。
Safe Region是指在一段代碼片段中,對(duì)象的引用關(guān)系不會(huì)發(fā)生變化,在這個(gè)區(qū)域中的任何位置開始GC都是安全的。1、當(dāng)線程運(yùn)行到Safe Region的代碼時(shí),首先標(biāo)識(shí)已經(jīng)進(jìn)入了Safe Region,如果這段時(shí)間內(nèi)發(fā)生GC,JVM會(huì)忽略標(biāo)識(shí)為Safe Region狀態(tài)的線程;2、當(dāng)線程即將離開Safe Region時(shí),會(huì)檢查JVM是否已經(jīng)完成GC,如果完成了,則繼續(xù)運(yùn)行,否則線程必須等待直到收到可以安全離開Safe Region的信號(hào)為止;
Java虛擬機(jī)規(guī)范并沒有規(guī)定垃圾收集器應(yīng)該如何實(shí)現(xiàn),用戶可以根據(jù)系統(tǒng)特點(diǎn)對(duì)各個(gè)區(qū)域所使用的收集器進(jìn)行組合使用。
上圖展示了7種不同分代的收集器,如果兩兩之間存在連線,說明可以組合使用。
1、Serial收集器(串行GC)
Serial 是一個(gè)采用單個(gè)線程并基于復(fù)制算法工作在新生代的收集器,進(jìn)行垃圾收集時(shí),必須暫停其他所有的工作線程。對(duì)于單CPU環(huán)境來說,Serial由于沒有線程交互的開銷,可以很高效的進(jìn)行垃圾收集動(dòng)作,是Client模式下新生代默認(rèn)的收集器。
2、ParNew收集器(并行GC)
ParNew其實(shí)是serial的多線程版本,除了使用多條線程進(jìn)行垃圾收集之外,其余行為與Serial一樣。
3、Parallel Scavenge收集器(并行回收GC)
Parallel Scavenge是一個(gè)采用多線程基于復(fù)制算法并工作在新生代的收集器,其關(guān)注點(diǎn)在于達(dá)到一個(gè)可控的吞吐量,經(jīng)常被稱為“吞吐量?jī)?yōu)先”的收集器。
吞吐量 = 用戶代碼運(yùn)行時(shí)間 /(用戶代碼運(yùn)行時(shí)間 + 垃圾收集時(shí)間)
Parallel Scavenge提供了兩個(gè)參數(shù)用于精確控制吞吐量:1、-XX:MaxGCPauseMillis 設(shè)置垃圾收集的最大停頓時(shí)間2、-XX:GCTimeRatio 設(shè)置吞吐量大小
4、Serial Old收集器(串行GC)
Serial Old 是一個(gè)采用單線程基于標(biāo)記-整理算法并工作在老年代的收集器,是Client模式下老年代默認(rèn)的收集器。
5、Parallel Old收集器(并行GC)
Parallel Old是一個(gè)采用多線程基于標(biāo)記-整理算法并工作在老年代的收集器。在注重吞吐量以及CPU資源敏感的場(chǎng)合,可以優(yōu)先考慮Parallel Scavenge和Parallel Old的收集器組合。
6、CMS收集器(并發(fā)GC)
CMS(Concurrent Mark Sweep)是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器,工作在老年代,基于“標(biāo)記-清除”算法實(shí)現(xiàn),整個(gè)過程分為以下4步:
1、初始標(biāo)記:這個(gè)過程只是標(biāo)記以下GC Roots能夠直接關(guān)聯(lián)的對(duì)象,但是仍然會(huì)Stop The World;2、并發(fā)標(biāo)記:進(jìn)行GC Roots Tracing的過程,可以和用戶線程一起工作。3、重新標(biāo)記:用于修正并發(fā)標(biāo)記期間由于用戶程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那部分記錄,這個(gè)過程會(huì)暫停所有線程,但其停頓時(shí)間遠(yuǎn)比并發(fā)標(biāo)記的時(shí)間短;4、并發(fā)清理:可以和用戶線程一起工作。
CMS收集器的缺點(diǎn):
1、對(duì)CPU資源比較敏感,在并發(fā)階段,雖然不會(huì)導(dǎo)致用戶線程停頓,但是會(huì)占用一部分線程資源,降低系統(tǒng)的總吞吐量。2、無(wú)法處理浮動(dòng)垃圾,在并發(fā)清理階段,用戶線程的運(yùn)行依然會(huì)產(chǎn)生新的垃圾對(duì)象,這部分垃圾只能在下一次GC時(shí)收集。3、CMS是基于標(biāo)記-清除算法實(shí)現(xiàn)的,意味著收集結(jié)束后會(huì)造成大量的內(nèi)存碎片,可能導(dǎo)致出現(xiàn)老年代剩余空間很大,卻無(wú)法找到足夠大的連續(xù)空間分配當(dāng)前對(duì)象,不得不提前觸發(fā)一次Full GC。
JDK1.5實(shí)現(xiàn)中,當(dāng)老年代空間使用率達(dá)到68%時(shí),就會(huì)觸發(fā)CMS收集器,如果應(yīng)用中老年代增長(zhǎng)不是太快,可以通過-XX:CMSInitiatingOccupancyFraction參數(shù)提高觸發(fā)百分比,從而降低內(nèi)存回收次數(shù)提高系統(tǒng)性能。
JDK1.6實(shí)現(xiàn)中,觸發(fā)CMS收集器的閾值已經(jīng)提升到92%,要是CMS運(yùn)行期間預(yù)留的內(nèi)存無(wú)法滿足用戶線程需要,會(huì)出現(xiàn)一次”Concurrent Mode Failure”失敗,這是虛擬機(jī)會(huì)啟動(dòng)Serial Old收集器對(duì)老年代進(jìn)行垃圾收集,當(dāng)然,這樣應(yīng)用的停頓時(shí)間就更長(zhǎng)了,所以這個(gè)閾值也不能設(shè)置的太高,如果導(dǎo)致了”Concurrent Mode Failure”失敗,反而會(huì)降低性能,至于如何設(shè)置這個(gè)閾值,還得長(zhǎng)時(shí)間的對(duì)老年代空間的使用情況進(jìn)行監(jiān)控。
7、G1收集器
G1(Garbage First)是JDK1.7提供的一個(gè)工作在新生代和老年代的收集器,基于“標(biāo)記-整理”算法實(shí)現(xiàn),在收集結(jié)束后可以避免內(nèi)存碎片問題。
G1優(yōu)點(diǎn):
1、并行與并發(fā):充分利用多CPU來縮短Stop The World的停頓時(shí)間;2、分代收集:不需要其他收集配合就可以管理整個(gè)Java堆,采用不同的方式處理新建的對(duì)象、已經(jīng)存活一段時(shí)間和經(jīng)歷過多次GC的對(duì)象獲取更好的收集效果;3、空間整合:與CMS的”標(biāo)記-清除”算法不同,G1在運(yùn)行期間不會(huì)產(chǎn)生內(nèi)存空間碎片,有利于應(yīng)用的長(zhǎng)時(shí)間運(yùn)行,且分配大對(duì)象時(shí),不會(huì)導(dǎo)致由于無(wú)法申請(qǐng)到足夠大的連續(xù)內(nèi)存而提前觸發(fā)一次Full GC;4、停頓預(yù)測(cè):G1中可以建立可預(yù)測(cè)的停頓時(shí)間模型,能讓使用者明確指定在M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不得超過N毫秒。
使用G1收集器時(shí),Java堆的內(nèi)存布局與其他收集器有很大區(qū)別,整個(gè)Java堆會(huì)被劃分為多個(gè)大小相等的獨(dú)立區(qū)域Region,新生代和老年代不再是物理隔離了,都是一部分Region(不需要連續(xù))的集合。G1會(huì)跟蹤各個(gè)Region的垃圾收集情況(回收空間大小和回收消耗的時(shí)間),維護(hù)一個(gè)優(yōu)先列表,根據(jù)允許的收集時(shí)間,優(yōu)先回收價(jià)值最大的Region,避免在整個(gè)Java堆上進(jìn)行全區(qū)域的垃圾回收,確保了G1收集器可以在有限的時(shí)間內(nèi)盡可能收集更多的垃圾。
不過問題來了:使用G1收集器,一個(gè)對(duì)象分配在某個(gè)Region中,可以和Java堆上任意的對(duì)象有引用關(guān)系,那么如何判定一個(gè)對(duì)象是否存活,是否需要掃描整個(gè)Java堆?其實(shí)這個(gè)問題在之前收集器中也存在,如果回收新生代的對(duì)象時(shí),不得不同時(shí)掃描老年代的話,會(huì)大大降低Minor GC的效率。
針對(duì)這種情況,虛擬機(jī)提供了一個(gè)解決方案:G1收集器中Region之間的對(duì)象引用關(guān)系和其他收集器中新生代與老年代之間的對(duì)象引用關(guān)系被保存在Remenbered Set數(shù)據(jù)結(jié)構(gòu)中,用來避免全堆掃描。G1中每個(gè)Region都有一個(gè)對(duì)應(yīng)的Remenbered Set,當(dāng)虛擬機(jī)發(fā)現(xiàn)程序?qū)eference類型的數(shù)據(jù)進(jìn)行寫操作時(shí),會(huì)產(chǎn)生一個(gè)Write Barrier暫時(shí)中斷寫操作,檢查Reference引用的對(duì)象是否處于相同的Region中,如果不是,則通過CardTable把相關(guān)引用信息記錄到被引用對(duì)象所屬Region的Remenbered Set中。
學(xué)習(xí)Java的同學(xué)注意了?。?! 學(xué)習(xí)過程中遇到什么問題或者想獲取學(xué)習(xí)資源的話,歡迎加入Java學(xué)習(xí)交流群,群號(hào)碼:183993990 我們一起學(xué)Java!
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注