最糟糕的是人們?cè)谏钪薪?jīng)常受到錯(cuò)誤志向的阻礙而不自知茉帅,真到擺脫了那些阻礙時(shí)才能明白過來。 —— 歌德
1. 對(duì)象的標(biāo)記過程
1.1 快速枚舉GC Roots
在可達(dá)性分析過程中,為了準(zhǔn)確找出與GC Roots相關(guān)聯(lián)的對(duì)象,必須要求整個(gè)執(zhí)行系統(tǒng)看起來像是被凍結(jié)在某個(gè)時(shí)間點(diǎn)上,即暫停所有運(yùn)行中的線程懂算,不可以出現(xiàn)對(duì)象的引用關(guān)系還在不斷變化的情況,這點(diǎn)是導(dǎo)致GC時(shí)必須停頓所有線程(“Stop the world”)的一個(gè)重要原因夜涕。
??可作為GC Roots的節(jié)點(diǎn)主要在全局性的引用(例如常量或靜態(tài)類屬性)與執(zhí)行上下文(本地變量表中的引用)中犯犁。
??在HosSpots中,是使用一組稱為OopMap的數(shù)據(jù)結(jié)構(gòu)來存放著對(duì)象的引用女器。類加載完成時(shí),HotSpot把對(duì)象內(nèi)什么偏移量上是什么類型的數(shù)據(jù)計(jì)算出來存儲(chǔ)到OopMap中住诸,通過JIT編譯出來的本地代碼驾胆,也會(huì)記錄下棧和寄存器中哪些位置是引用。通過掃描OopMap的數(shù)據(jù)就可以快速標(biāo)識(shí)出存活的對(duì)象贱呐。
1.2 安全的進(jìn)行GC
在發(fā)生GC時(shí)丧诺,JVM通過安全點(diǎn)和安全區(qū)域來保證GC可以安全的進(jìn)行。
1.2.1 安全點(diǎn)
線程運(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處記錄這些信息蜘腌。
??當(dāng)發(fā)生GC時(shí),不直接對(duì)線程進(jìn)行中斷操作饵隙,而是簡單的設(shè)置一個(gè)中斷標(biāo)志撮珠,每個(gè)線程運(yùn)行到Safe Point的時(shí)候,主動(dòng)去輪詢這個(gè)中斷標(biāo)志金矛,如果中斷標(biāo)志為真芯急,則將自己進(jìn)行中斷掛起。
1.2.2 安全區(qū)域
處于Sleep或Blocked狀態(tài)的線程在GC時(shí)無法響應(yīng)JVM的中斷請(qǐng)求驶俊,無法到Safe Point去中斷掛起娶耍,對(duì)于這種情況,可以使用安全區(qū)域(Safe Region)來解決饼酿。
??Safe Region是指在一段代碼片段中伺绽,對(duì)象的引用關(guān)系不會(huì)發(fā)生變化,在這個(gè)區(qū)域中的任何位置開始GC都是安全的嗜湃。
- 當(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)的線程杖挣。
- 當(dāng)線程即將離開Safe Region時(shí),會(huì)檢查JVM是否已經(jīng)完成GC刚陡,如果完成了惩妇,則繼續(xù)運(yùn)行,否則線程必須等待直到收到可以安全離開Safe Region的信號(hào)為止筐乳。
2. 垃圾收集器
垃圾收集器是內(nèi)存回收的具體實(shí)現(xiàn)歌殃,JAVA虛擬機(jī)沒有規(guī)定垃圾收集器該如何實(shí)現(xiàn),且提供參數(shù)供用戶根據(jù)自己的要求將垃圾收集器進(jìn)行組合使用蝙云。
??上圖展示了7種不同的分代收集器氓皱,若兩個(gè)收集器直接存在連線,則說明可以組合使用勃刨。虛擬機(jī)所處的區(qū)域波材,則表示是屬于新生代收集器還是老年代收集器。
2.1 Serial收集器(新生代串行收集器)
串行收集器主要有兩個(gè)特點(diǎn):第一身隐,僅使用單線程進(jìn)行垃圾回收廷区;第二,它是獨(dú)占式的垃圾回收贾铝。
??之所以是獨(dú)占式的隙轻,是因?yàn)樵诖惺占鬟\(yùn)行時(shí)埠帕,應(yīng)用程序的所有線程都停止工作,進(jìn)行等待玖绿。
??雖然如此敛瓷,串行收集器卻是一個(gè)極為高效的收集器,使用的是復(fù)制算法镰矿,實(shí)現(xiàn)相對(duì)簡單琐驴,且沒有線程的切換開銷。
??使用-XX:+UseSerialGC參數(shù)可以指定使用新生代串行收集器和老年代串行收集器秤标。當(dāng)JVM在Client模式下绝淡,Serial收集器是默認(rèn)的垃圾收集器。
2.2 ParNew收集器(并行收集器)
并行收集器工作在新生代苍姜,只是簡單的將串行收集器多線程化牢酵,回收策略、算法以及參數(shù)和串行收集器是一樣的衙猪。并行收集器也是獨(dú)占式的馍乙,在GC過程中,應(yīng)用程序也會(huì)全部暫停垫释,由于使用多線程進(jìn)行垃圾回收丝格,因此產(chǎn)生的停頓時(shí)間要短于串行收集器。
??開啟并行收集器可以使用以下參數(shù):
??(1) -XX:UseParNewGC: 新生代使用并行收集器棵譬,老年代使用串行收集器显蝌。
??(2) -XX:UseConcMarkSweepGC: 新生代使用并行收集器,老年代使用CMS订咸。
??并行收集器GC時(shí)的線程數(shù)量可以使用-XX:ParallelGCThreads參數(shù)指定曼尊。
2.3 新生代并行回收(Parallel Scavenge)收集器
Parallel Scavenge收集器是一個(gè)采用多線程基于復(fù)制算法并工作在新生代的收集器,其中一個(gè)特點(diǎn)是它比較關(guān)注系統(tǒng)的吞吐量脏嚷。
??吞吐量 = 用戶代碼運(yùn)行時(shí)間 /(用戶代碼運(yùn)行時(shí)間 + 垃圾收集時(shí)間)
??使用以下參數(shù)可以開啟Parallel Scavenge:
??(1) -XX:+UseParallelGC: 新生代使用Parallel Scavenge骆撇,老年代使用串行收集器。
??(2) -XX:+UseParallelOldGC: 新生代和老年代都使用并行回收收集器父叙。
??Parallel Scavenge提供了兩個(gè)參數(shù)用于精確控制吞吐量:
??1神郊、-XX:MaxGCPauseMillis 設(shè)置垃圾收集的最大停頓時(shí)間。
??2高每、-XX:GCTimeRatio 設(shè)置吞吐量大小屿岂。
??除此之外,Parallel Scavenge還支持自適應(yīng)的GC調(diào)節(jié)策略鲸匿,使用-XX:UseAdaptiveSizePolicy可以打開自適應(yīng)的GC調(diào)節(jié)策略。在這種模式下阻肩,新生代的大小带欢,eden和survivor的比例运授,晉升老年代的對(duì)象年齡等參數(shù)會(huì)被自動(dòng)調(diào)整,以達(dá)到系統(tǒng)運(yùn)行的最優(yōu)化乔煞。
2.4 Serial Old收集器
Serial Old 是一個(gè)采用單線程基于標(biāo)記-整理算法并工作在老年代的收集器吁朦,是Client模式下老年代默認(rèn)的收集器。
2.5 Parallel Old收集器(并行GC)
Parallel Old是一個(gè)采用多線程基于標(biāo)記-整理算法并工作在老年代的收集器渡贾。同樣也是一個(gè)關(guān)注系統(tǒng)吞吐量的收集器逗宜。
2.6 CMS收集器
CMS主要關(guān)注系統(tǒng)的停頓時(shí)間,使用的是標(biāo)記-清除算法空骚,工作在老年代纺讲,GC時(shí),分為以下四個(gè)階段:
??(1) 初始標(biāo)記:這個(gè)過程只是標(biāo)記需要回收的對(duì)象囤屹,這個(gè)階段仍然會(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ì)Stop The World。
??(4) 并發(fā)清除:多個(gè)線程并行進(jìn)行垃圾回收智厌,可以和用戶線程一起工作诲泌。
??CMS收集器的缺點(diǎn):
??(1) 對(duì)CPU資源比較敏感,在并發(fā)階段铣鹏,雖然不會(huì)導(dǎo)致用戶線程停頓敷扫,但是會(huì)占用一部分線程資源,降低系統(tǒng)的總吞吐量吝沫。
??(2) 無法處理浮動(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)老年代剩余空間很大栅受,卻無法找到足夠大的連續(xù)空間分配當(dāng)前對(duì)象,不得不提前觸發(fā)一次Full GC恭朗。
2.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) 空間整合:G1在運(yùn)行期間不會(huì)產(chǎn)生內(nèi)存空間碎片,有利于應(yīng)用的長時(shí)間運(yùn)行歌逢,且分配大對(duì)象時(shí)巾钉,不會(huì)導(dǎo)致由于無法申請(qǐng)到足夠大的連續(xù)內(nèi)存而提前觸發(fā)一次Full GC。
??(4) 可預(yù)測的停頓:G1中可以建立可預(yù)測的停頓時(shí)間模型秘案,能讓使用者明確指定在M毫秒的時(shí)間片段內(nèi)砰苍,消耗在垃圾收集上的時(shí)間不得超過N毫秒。
??使用G1收集器時(shí)阱高,Java堆的內(nèi)存布局與其他收集器有很大區(qū)別赚导,它將整個(gè)Java堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region)臭胜,新生代和老年代不再試物理隔離了插佛,都是一部分Region(不需要連續(xù))的集合。G1跟蹤各個(gè)Region里的垃圾堆積的大小巍棱,在后臺(tái)維護(hù)一個(gè)優(yōu)先列表荐捻,根據(jù)允許的收集時(shí)間黍少,優(yōu)先回收價(jià)值最大的Region,避免在整個(gè)Java堆上進(jìn)行全區(qū)域的垃圾回收处面,確保了G1收集器可以在有限的時(shí)間內(nèi)盡可能收集更多的垃圾厂置。
??在G1收集器中,Region之間的對(duì)象引用以及其他收集器中的新生代和老年代之間的對(duì)象引用魂角,JVM都是使用Remembered Set數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)昵济,以避免全堆掃描。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中。
??G1收集器GC時(shí)挣惰,主要分為以下四個(gè)階段:
??(1) 初始標(biāo)記:標(biāo)記與GC Roots直接相關(guān)聯(lián)的對(duì)象卧斟,該階段會(huì)Stop the world。
??(2)并發(fā)標(biāo)記:從 GC Roots開始對(duì)堆中的對(duì)象進(jìn)行可達(dá)性分析憎茂,找出存活對(duì)象珍语,可與應(yīng)用線程并發(fā)執(zhí)行。
??(3)最終標(biāo)記:為了修正在并發(fā)標(biāo)記期間因用戶線程執(zhí)行導(dǎo)致標(biāo)記產(chǎn)生變化的那一部分標(biāo)記記錄竖幔。
??(4)篩選回收:對(duì)各個(gè)Region的回收價(jià)值和成本進(jìn)行排序板乙,根據(jù)用戶所期望的GC停頓時(shí)間來制定回收計(jì)劃。