垃圾收集算法
一耐量、 標(biāo)記-清除算法(Mark-Sweep)
算法分為“標(biāo)記”和“清除”兩個(gè)階段,首先標(biāo)記出所有需要回收的對(duì)象哺眯,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象。標(biāo)記判定為可達(dá)性分析算法秘血。
不足之處有兩個(gè):
- 效率問(wèn)題,標(biāo)記和清除兩個(gè)過(guò)程的效率都不高评甜;
- 空間問(wèn)題灰粮,標(biāo)記清除之后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太對(duì)可能會(huì)導(dǎo)致以后在程序運(yùn)行的過(guò)程中需要分配較大的對(duì)象時(shí)忍坷,無(wú)法找到足夠的連續(xù)內(nèi)存而不得不提前出發(fā)另一次垃圾收集動(dòng)作粘舟。
二、 復(fù)制算法(Copying)
為了解決效率問(wèn)題佩研,出現(xiàn)了復(fù)制算法柑肴,將可用的容量劃分為大小相等的兩塊,每次使用其中一塊旬薯,當(dāng)這塊內(nèi)存用完了晰骑,就將還存貨的對(duì)象復(fù)制到另一塊上面,然后再把已使用的內(nèi)存空間一次性清理掉绊序。硕舆,內(nèi)存分配不用考慮內(nèi)存碎片等復(fù)雜情況,只要移動(dòng)堆頂指針政模,按順序分配內(nèi)存即可岗宣,實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效淋样。算法代價(jià)為內(nèi)存縮小到原來(lái)的一半。
三胁住、 標(biāo)記-整理算法(Mark-Compact)
復(fù)制算法在對(duì)象存活率較高時(shí)趁猴,就要進(jìn)行較多的復(fù)制操作,效率會(huì)變低彪见。更關(guān)鍵的是會(huì)浪費(fèi)50%空間儡司。所以老年代一般不能直接選用這種算法挪捕。
根據(jù)老年代的特點(diǎn)智厌,就提出了一種“標(biāo)記-整理”算法易结,整理不是直接對(duì)可回收對(duì)象進(jìn)行清理凹蜂,而是讓所有存活對(duì)象向一端移動(dòng)醉锅,然后直接清理掉端邊界以外的內(nèi)存贮庞。
四边苹、分代收集算法(Generational Collection)
根據(jù)對(duì)象存活周期的不同凉夯,將內(nèi)存劃分為幾塊淮韭。一般是新生代和年老代垢粮。新生代采用復(fù)制算法。老年代使用“標(biāo)記—清理”或者“標(biāo)記-整理”算法靠粪。
新生代中的對(duì)象98%都是“朝生夕死”的蜡吧,所以不需要1:1比例來(lái)劃分內(nèi)存空間毫蚓,而是將內(nèi)存分為一塊較大的Eden空間和兩個(gè)較小的Survivor空間,每次使用Eden和其中一塊Survivor昔善。當(dāng)回收時(shí)元潘,將Eden區(qū)和Survivor還存活著的對(duì)象一次性地賦值到另外一塊Survivor空間上,然后清理到Eden和剛剛用過(guò)的Survivor空間君仆。
HotSpot算法實(shí)現(xiàn)
1柬批、枚舉根節(jié)點(diǎn)
可達(dá)性分析有一個(gè)問(wèn)題就是會(huì)導(dǎo)致GC進(jìn)行時(shí)必須停止Java執(zhí)行線程,因?yàn)槊杜e根節(jié)點(diǎn)的時(shí)候?qū)ο箨P(guān)系不斷變化時(shí)袖订,分析結(jié)果準(zhǔn)確性就不能得到保證氮帐。即時(shí)是CMS收集器,枚舉根節(jié)點(diǎn)也是必須要停頓的洛姑。
在HotStop的實(shí)現(xiàn)中上沐,使用一組稱為OooMap數(shù)據(jù)結(jié)構(gòu)來(lái)達(dá)到這個(gè)目的,在類加載的時(shí)候HotStop就把對(duì)象內(nèi)什么偏移量上是什么類型的數(shù)據(jù)計(jì)算出來(lái)楞艾,在JIT編譯過(guò)程中参咙,也會(huì)在特定的位置記錄下棧和寄存器中哪些位置是引用。這樣GC在掃描的時(shí)候就可以直接得知這些信息了硫眯。
2蕴侧、安全點(diǎn)及安全區(qū)域
在OopMap的協(xié)助下,HotSpot可以快速且準(zhǔn)確的完成GC Roots的枚舉两入,但是HotSpot并沒(méi)有為每條指令都生成OopMap,因?yàn)檫@樣需要的額外空間太多净宵。
而是在特定的位置記錄了這些信息,這些位置稱為安全點(diǎn)(Safepoint)裹纳,即程序執(zhí)行的時(shí)候并非在所有的地方都能停頓下來(lái)择葡,而是在到達(dá)安全段時(shí)才能暫停。
安全點(diǎn)的選定基于“是否具有讓程序長(zhǎng)時(shí)間執(zhí)行的特征”進(jìn)行選定的剃氧。因?yàn)槊織l指令執(zhí)行的時(shí)間都非常短暫敏储,程序不太可能因?yàn)橹噶盍鏖L(zhǎng)度太長(zhǎng)而過(guò)長(zhǎng)時(shí)間運(yùn)行,“長(zhǎng)時(shí)間運(yùn)行”最明顯的特征就是指令序列復(fù)用朋鞍。例如方法調(diào)用已添,循環(huán)跳轉(zhuǎn),異常跳轉(zhuǎn)等滥酥,具有這種功能的指令才會(huì)產(chǎn)生Safepoint更舞。
還有一個(gè)問(wèn)題,如何在GC時(shí)使所有的線程都跑到最近的安全點(diǎn)再停頓下來(lái)恨狈。兩個(gè)方案
- 搶先式中斷
不需要線程主動(dòng)配合疏哗,而是先把所有線程都中斷,然后如果不在安全段就恢復(fù)線程,讓它跑到安全點(diǎn)》捣睿現(xiàn)在基本沒(méi)有這種實(shí)現(xiàn)方式贝搁。 - 主動(dòng)式中斷
當(dāng)GC需要中斷線程時(shí),不直接對(duì)線程操作芽偏,而是在安全點(diǎn)以及創(chuàng)建對(duì)象需要分配內(nèi)存的地方設(shè)置一個(gè)標(biāo)志雷逆,各個(gè)線程執(zhí)行時(shí)主動(dòng)去輪詢這個(gè)標(biāo)志,發(fā)現(xiàn)中斷標(biāo)志為真時(shí)就自己中斷線程掛起污尉。
安全點(diǎn)并沒(méi)有解決膀哲,程序不執(zhí)行時(shí)如何跑到安全點(diǎn)的問(wèn)題。因?yàn)檫@種一般是線程處于sleep狀態(tài)或者Blocked狀態(tài)被碗,這個(gè)時(shí)候線程無(wú)法相應(yīng)JVM的中斷請(qǐng)求某宪。
這個(gè)時(shí)候就需要安全區(qū)域了,安全區(qū)域是指一段代碼片段中锐朴,引用關(guān)系不會(huì)發(fā)生變化兴喂,這個(gè)區(qū)域任意時(shí)刻開(kāi)始GC都是安全的。安全區(qū)域可以看作是安全點(diǎn)的擴(kuò)展焚志。
當(dāng)線程執(zhí)行到安全區(qū)域的代碼時(shí)衣迷,首先標(biāo)識(shí)自己進(jìn)入安全區(qū)域了,這時(shí)當(dāng)GC時(shí)酱酬,JVM就不用管標(biāo)識(shí)為安全區(qū)域狀態(tài)的線程了壶谒。
垃圾收集器
圖中展示了7種不同分代的收集器:
Serial、ParNew膳沽、Parallel Scavenge汗菜、Serial Old、Parallel Old贵少、CMS呵俏、G1;
而它們所處區(qū)域滔灶,則表明其是屬于新生代收集器還是老年代收集器:
- 新生代收集器:Serial、ParNew吼肥、Parallel Scavenge录平;
- 老年代收集器:Serial Old、Parallel Old缀皱、CMS斗这;
- 整堆收集器:G1;
兩個(gè)收集器間有連線啤斗,表明它們可以搭配使用:
Serial/Serial Old表箭、Serial/CMS、ParNew/Serial Old钮莲、ParNew/CMS免钻、Parallel Scavenge/Serial Old彼水、Parallel Scavenge/Parallel Old、G1极舔;
并發(fā)垃圾收集和并行垃圾收集的區(qū)別
- 并行(Parallel)
指多條垃圾收集線程并行工作凤覆,但此時(shí)用戶線程仍然處于等待狀態(tài);
如ParNew拆魏、Parallel Scavenge盯桦、Parallel Old; - 并發(fā)(Concurrent)
指用戶線程與垃圾收集線程同時(shí)執(zhí)行(但不一定是并行的渤刃,可能會(huì)交替執(zhí)行)拥峦;
用戶程序在繼續(xù)運(yùn)行,而垃圾收集程序線程運(yùn)行于另一個(gè)CPU上卖子;
如CMS略号、G1(也有并行)。
Minor GC和Full GC的區(qū)別
- Minor GC又稱新生代GC揪胃,指發(fā)生在新生代的垃圾收集動(dòng)作璃哟;
因?yàn)镴ava對(duì)象大多是朝生夕滅,所以Minor GC非常頻繁喊递,一般回收速度也比較快随闪; - Full GC又稱Major GC或老年代GC,指發(fā)生在老年代的GC骚勘;
出現(xiàn)Full GC經(jīng)常會(huì)伴隨至少一次的Minor GC(不是絕對(duì)铐伴,Parallel Sacvenge收集器就可以選擇設(shè)置Major GC策略);
Major GC速度一般比Minor GC慢10倍以上俏讹;
1当宴、 Serial收集器
Serial收集器是最基本、發(fā)展歷史最悠久的收集器泽疆。
特點(diǎn)
- 針對(duì)新生代
- 采用復(fù)制算法
- 單線程收集
-
進(jìn)行垃圾回收時(shí)户矢,必須暫停所有工作線程,直到完成(Stop The World)殉疼。
優(yōu)點(diǎn)以及應(yīng)用場(chǎng)景
- 虛擬機(jī)運(yùn)行在client模式下默認(rèn)新生代收集器
- 簡(jiǎn)單高效(與其他收集器的單線程比)
參數(shù)設(shè)置
“-XX:UseSerialGC”:添加該參數(shù)來(lái)顯式的使用串行垃圾收集器梯浪;
2、ParNew收集器
ParNew收集器是Serial收集器的多線程版本瓢娜。
特點(diǎn)
- 除了使用多線程進(jìn)行垃圾收集以外挂洛,其余行為和特點(diǎn)和Serial收集器一樣
-
兩者共用了很多相同的代碼
應(yīng)用場(chǎng)景
- 在server模式下,ParNew是一個(gè)很重要的收集器眠砾,因?yàn)槌薙erial收集器虏劲,目前只有它能夠與CMS收集器配合工作。
- 在單個(gè)cup的環(huán)境下,ParNew效率比Serial低柒巫,因?yàn)榇嬖诰€程交互開(kāi)銷励堡。
設(shè)置參數(shù)
- "-XX:+UseConcMarkSweepGC":指定使用CMS后,會(huì)默認(rèn)使用ParNew作為新生代收集器吻育;
- "-XX:+UseParNewGC":強(qiáng)制指定使用ParNew念秧;
- "-XX:ParallelGCThreads":指定垃圾收集的線程數(shù)量,ParNew默認(rèn)開(kāi)啟的收集線程與CPU的數(shù)量相同布疼;
為什么除了Serial只有ParNew能與CMS收集器配合
- CMS作為老年代收集器摊趾,但卻無(wú)法與JDK1.4已經(jīng)存在的新生代收集器Parallel Scavenge配合工作;
- 因?yàn)镻arallel Scavenge(以及G1)都沒(méi)有使用傳統(tǒng)的GC收集器代碼框架游两,而另外獨(dú)立實(shí)現(xiàn)砾层;而其余幾種收集器則共用了部分的框架代碼;
3贱案、 Parallel Scavenge收集器
Parallel Scavenge收集器是一個(gè)新生代收集器肛炮,也是使用復(fù)制算法以及并行的多線程收集器。它與吞吐量密切相關(guān)宝踪,也稱為吞吐量收集器(ThroughPut Collector)
特點(diǎn)
- 前面寫的侨糟,與ParNew相似,新生代瘩燥、復(fù)制秕重、多線程收集;
- 它的關(guān)注點(diǎn)與其他收集器不同厉膀,其他都是盡可能地縮短垃圾收集時(shí)用戶線程的停頓時(shí)間溶耘,而它的目標(biāo)是達(dá)到一個(gè)可以控制的吞吐量(Throughput)。
吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+垃圾回收時(shí)間)
應(yīng)用場(chǎng)景
- 高吞吐量為目標(biāo)服鹅,可以高效率地利用CUP時(shí)間凳兵,盡快地完成計(jì)算任務(wù),主要適合后臺(tái)運(yùn)行且不需要太多交互的任務(wù)企软。例如庐扫,那些執(zhí)行批量處理、訂單處理仗哨、工資支付聚蝶、科學(xué)計(jì)算的應(yīng)用程序。
設(shè)置參數(shù)
- "-XX:MaxGCPauseMillis":控制最大垃圾收集停頓時(shí)間藻治,大于0的毫秒數(shù);
- "-XX:GCTimeRatio": 設(shè)置垃圾收集時(shí)間占總時(shí)間的比率巷挥,相當(dāng)于吞吐量的倒數(shù)桩卵,0<n<100的整數(shù);
垃圾收集執(zhí)行時(shí)間占應(yīng)用程序執(zhí)行時(shí)間的比例的計(jì)算方法是:
1 / (1 + n)
例如,選項(xiàng)-XX:GCTimeRatio=19雏节,設(shè)置了垃圾收集時(shí)間占總時(shí)間的5% -- 1/(1+19)胜嗓;
默認(rèn)值是1%--1/(1+99),即n=99钩乍; - "-XX:+UseAdptiveSizePolicy":采用自適應(yīng)調(diào)節(jié)策略
開(kāi)啟這個(gè)參數(shù)后辞州,就不用手工指定一些細(xì)節(jié)參數(shù),如:新生代的大辛却狻(-Xmn)变过、Eden與Survivor區(qū)的比例(-XX:SurvivorRation)、晉升老年代的對(duì)象年齡(-XX: PretenureSizeThreshold)等涝涤;
JVM會(huì)根據(jù)當(dāng)前系統(tǒng)運(yùn)行情況收集性能監(jiān)控信息媚狰,動(dòng)態(tài)調(diào)整這些參數(shù),以提供最合適的停頓時(shí)間或最大的吞吐量阔拳,這種調(diào)節(jié)方式稱為GC自適應(yīng)的調(diào)節(jié)策略(GC Ergonomiscs)
這是一種值得推薦的方式: - 只需設(shè)置好內(nèi)存數(shù)據(jù)大姓腹隆(如"-Xmx"設(shè)置最大堆);
- 然后使用"-XX:MaxGCPauseMillis"或"-XX:GCTimeRatio"給JVM設(shè)置一個(gè)優(yōu)化目標(biāo)糊肠;
- 那些具體細(xì)節(jié)參數(shù)的調(diào)節(jié)就由JVM自適應(yīng)完成辨宠;
這也是Parallel Scavenge收集器與ParNew收集器一個(gè)重要區(qū)別;
四货裹、Serial Old收集器
Serial Old是 Serial收集器的老年代版本嗤形;
特點(diǎn)
- 針對(duì)老年代;
- 采用"標(biāo)記-整理"算法(還有壓縮泪酱,Mark-Sweep-Compact)派殷;
- 單線程收集;
應(yīng)用場(chǎng)景
- 主要應(yīng)用于Client模式墓阀;
- 在JDK1.5及之前毡惜,與Parallel Scavenge收集器搭配使用(JDK1.6有Parallel Old收集器可搭配;
- 作為CMS收集器的后備預(yù)案斯撮,在并發(fā)收集發(fā)生Concurrent Mode Failure時(shí)使用
五经伙、 Parallel Old收集器
Parallel Old垃圾收集器是Parallel Scavenge收集器的老年代版本;JDK1.6中才開(kāi)始提供勿锅;
特點(diǎn)
- 針對(duì)老年代帕膜;
- 采用"標(biāo)記-整理"算法;
-
多線程收集溢十;
應(yīng)用場(chǎng)景
- JDK1.6及之后用來(lái)代替老年代的Serial Old收集器垮刹;
- 特別是在Server模式,多CPU的情況下张弛;
- 這樣在注重吞吐量以及CPU資源敏感的場(chǎng)景荒典,就有了Parallel Scavenge加Parallel Old收集器的"給力"應(yīng)用組合酪劫;
設(shè)置參數(shù)
- "-XX:+UseParallelOldGC":指定使用Parallel Old收集器;
六寺董、 CMS收集器(Concurrent Mark Sweep)
并發(fā)標(biāo)記清理(Concurrent Mark Sweep覆糟,CMS)收集器也稱為并發(fā)低停頓收集器(Concurrent Low Pause Collector)或低延遲(low-latency)垃圾收集器。
特點(diǎn)
- 針對(duì)老年代遮咖;
- 基于標(biāo)記-清除算法(不進(jìn)行壓縮操作滩字,會(huì)產(chǎn)生內(nèi)存碎片);
-以獲取最短回收停頓時(shí)間為目標(biāo)御吞; - 并發(fā)收集麦箍、低停頓
- 需要更多的內(nèi)存
應(yīng)用場(chǎng)景
- 與用戶交互場(chǎng)景較多的場(chǎng)景
- 希望系統(tǒng)停頓世家年最短,注重服務(wù)的響應(yīng)時(shí)間
- 比如場(chǎng)景的web魄藕,B/S系統(tǒng)的服務(wù)器上的應(yīng)用
設(shè)置參數(shù)
"-XX:+UseConcMarkSweepGC":指定使用CMS收集器内列;
CMS收集器運(yùn)行過(guò)程
- 1、 初始標(biāo)記(CMS initial mark)
僅標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象背率,速度很快话瞧,但是會(huì)Stop The World。 - 2 寝姿、并發(fā)標(biāo)記(CMS concurrent mark)
進(jìn)行GC Roots Tracing的過(guò)程交排,標(biāo)記出剛才產(chǎn)生的集合中活的對(duì)象,應(yīng)用程序在運(yùn)行饵筑,并不能保障可以標(biāo)記所有的存活對(duì)象埃篓。 - 3、重新標(biāo)記(CMS remark)
為了修正上一步并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)行導(dǎo)致標(biāo)記變動(dòng)的那部分對(duì)象的標(biāo)記記錄根资;
需要Stop The World 停頓時(shí)間比初始標(biāo)記稍長(zhǎng)架专,但遠(yuǎn)比并發(fā)標(biāo)記時(shí)間短;
采用多線程并行執(zhí)行來(lái)提升效率玄帕。 - 4部脚、并發(fā)清除(CMS concurrent sweep)
回收所有垃圾對(duì)象。
整個(gè)過(guò)程中耗時(shí)最長(zhǎng)的并發(fā)標(biāo)記和并發(fā)清除都可以與用戶線程一起工作裤纹;所以總體上說(shuō)委刘,CMS收集器的內(nèi)存回收過(guò)程與用戶線程一起并發(fā)執(zhí)行;
缺點(diǎn)
- 1鹰椒、對(duì)CPU資源非常敏感
并發(fā)收集雖然不會(huì)暫停用戶線程锡移,但因?yàn)檎加靡徊糠諧PU資源,還是會(huì)導(dǎo)致應(yīng)用程序變慢漆际,總吞吐量降低淆珊。
CMS的默認(rèn)收集線程數(shù)量是=(CPU數(shù)量+3)/4;
當(dāng)CPU數(shù)量多于4個(gè)奸汇,收集線程占用的CPU資源多于25%套蒂,對(duì)用戶程序影響可能較大钞支;不足4個(gè)時(shí),影響更大操刀,可能無(wú)法接受。 - 2婴洼、無(wú)法處理浮動(dòng)垃圾
在并發(fā)清除時(shí)骨坑,用戶線程新產(chǎn)生的垃圾,稱為浮動(dòng)垃圾柬采;只能留到下次GC清理欢唾,這使得并發(fā)清除時(shí)需要預(yù)留一定的內(nèi)存空間給用戶線程使用,不能像其他收集器在老年代幾乎填滿再進(jìn)行收集粉捻;
可以認(rèn)為CMS所需要的空間比其他垃圾收集器大礁遣;
"-XX:CMSInitiatingOccupancyFraction":設(shè)置CMS預(yù)留內(nèi)存空間;- JDK1.5默認(rèn)值為68%肩刃;
- JDK1.6變?yōu)榇蠹s92%祟霍;
- 可能出現(xiàn)"Concurrent Mode Failure"失敗而導(dǎo)致另一次Full GC 。
如果CMS預(yù)留內(nèi)存空間無(wú)法滿足程序需要盈包,就會(huì)出現(xiàn)一次"Concurrent Mode Failure"失敗;
這時(shí)JVM啟用后備預(yù)案:臨時(shí)啟用Serail Old收集器沸呐,而導(dǎo)致另一次Full GC的產(chǎn)生;
這樣的代價(jià)是很大的呢燥,所以CMSInitiatingOccupancyFraction不能設(shè)置得太大崭添。 - 3、產(chǎn)生大量?jī)?nèi)存碎片
由于CMS基于"標(biāo)記-清除"算法叛氨,清除后不進(jìn)行壓縮操作呼渣;
產(chǎn)生大量不連續(xù)的內(nèi)存碎片會(huì)導(dǎo)致分配大內(nèi)存對(duì)象時(shí),無(wú)法找到足夠的連續(xù)內(nèi)存寞埠,從而需要提前觸發(fā)另一次Full GC動(dòng)作屁置。- 解決辦法
- "-XX:+UseCMSCompactAtFullCollection":默認(rèn)開(kāi)啟
使得CMS出現(xiàn)上面這種情況時(shí)不進(jìn)行Full GC,而開(kāi)啟內(nèi)存碎片的合并整理過(guò)程畸裳;
但合并整理過(guò)程無(wú)法并發(fā)缰犁,停頓時(shí)間會(huì)變長(zhǎng); - "-XX:+CMSFullGCsBeforeCompaction":設(shè)置執(zhí)行多少次不壓縮的Full GC后怖糊,來(lái)一次壓縮整理帅容;
為減少合并整理過(guò)程的停頓時(shí)間;
默認(rèn)為0伍伤,也就是說(shuō)每次都執(zhí)行Full GC并徘,都會(huì)進(jìn)行壓縮整理;
總結(jié)
總體來(lái)看扰魂,與Parallel Old垃圾收集器相比麦乞,CMS減少了執(zhí)行老年代垃圾收集時(shí)應(yīng)用暫停時(shí)間蕴茴;
但卻增加了新生代垃圾收集時(shí)應(yīng)用暫停的時(shí)間、降低了吞吐量而且需要占用更大的堆空間姐直;
七倦淀、G1收集器(Garbage-First)
特點(diǎn)
- 1、并行與并發(fā)
- 面向服務(wù)器應(yīng)用的收集器
- 能充分利用多CPU声畏、多核環(huán)境下的硬件優(yōu)勢(shì)撞叽;
- 可以并行來(lái)縮短"Stop The World"停頓時(shí)間;
- 也可以并發(fā)讓垃圾收集與用戶程序同時(shí)進(jìn)行插龄;
- 2愿棋、分代收集
能獨(dú)立管理整個(gè)GC堆(新生代和老年代),而不需要與其他收集器搭配均牢;能夠采用不同方式處理不同時(shí)期的對(duì)象糠雨;
雖然保留分代概念,但Java堆的內(nèi)存布局有很大差別 將整個(gè)堆劃分為多個(gè)大小相等的獨(dú)立區(qū)域(Region)徘跪;新生代和老年代不再是物理隔離甘邀,它們都是一部分Region(不需要連續(xù))的集合; - 3真椿、 結(jié)合多種垃圾收集算法鹃答,空間整合,不產(chǎn)生碎片
從整體看突硝,是基于標(biāo)記-整理算法测摔;從局部(兩個(gè)Region間)看,是基于復(fù)制算法解恰;
這是一種類似火車算法的實(shí)現(xiàn)锋八;都不會(huì)產(chǎn)生內(nèi)存碎片,有利于長(zhǎng)時(shí)間運(yùn)行护盈; - 4挟纱、 可預(yù)測(cè)的停頓:低停頓的同時(shí)實(shí)現(xiàn)高吞吐量
G1除了追求低停頓處,還能建立可預(yù)測(cè)的停頓時(shí)間模型腐宋;
可以明確指定M毫秒時(shí)間片內(nèi)紊服,垃圾收集消耗的時(shí)間不超過(guò)N毫秒;
應(yīng)用場(chǎng)景
- 面向服務(wù)端應(yīng)用胸竞,針對(duì)具有大內(nèi)存欺嗤、多處理器的機(jī)器;
- 最主要的應(yīng)用是為需要低GC延遲卫枝,并具有大堆的應(yīng)用程序提供解決方案煎饼;
- 如:在堆大小約6GB或更大時(shí),可預(yù)測(cè)的暫停時(shí)間可以低于0.5秒校赤;
- 用來(lái)替換掉JDK1.5中的CMS收集器吆玖;
設(shè)置參數(shù)
- "-XX:+UseG1GC":指定使用G1收集器筒溃;
- "-XX:InitiatingHeapOccupancyPercent":當(dāng)整個(gè)Java堆的占用率達(dá)到參數(shù)值時(shí),開(kāi)始并發(fā)標(biāo)記階段沾乘;默認(rèn)為45怜奖;
- "-XX:MaxGCPauseMillis":為G1設(shè)置暫停時(shí)間目標(biāo),默認(rèn)值為200毫秒意鲸;
- "-XX:G1HeapRegionSize":設(shè)置每個(gè)Region大小烦周,范圍1MB到32MB;目標(biāo)是在最小Java堆時(shí)可以擁有約2048個(gè)Region怎顾;
為什么G1收集器可以實(shí)現(xiàn)可預(yù)測(cè)的停頓?
- 1、可以有計(jì)劃地避免在java堆上進(jìn)行全區(qū)域的垃圾收集漱贱;
G1跟蹤各個(gè)Region獲得其收集價(jià)值大谢蔽怼(回收所獲得的空間大小以及回收所需要時(shí)間的經(jīng)驗(yàn)值),在后臺(tái)維護(hù)一個(gè)優(yōu)先列表幅狮;每次根據(jù)允許的收集時(shí)間募强,優(yōu)先回收價(jià)值最大的Region(名稱Garbage-First的由來(lái))。這就保證了在有限的時(shí)間內(nèi)可以獲取盡可能高的收集效率崇摄;
一個(gè)對(duì)象被不同區(qū)域引用的問(wèn)題
判斷對(duì)象存活時(shí)擎值,是否需要掃描整個(gè)Java堆才能保證準(zhǔn)確?在其他的分代收集器逐抑,也存在這樣的問(wèn)題(而G1更突出):新生代回收的時(shí)候不得不掃描老年代鸠儿?
無(wú)論G1還是其他分代收集器,JVM都是使用Remembered Set來(lái)避免全局掃描:
1厕氨、每個(gè)Region都有一個(gè)對(duì)應(yīng)的Remembered Set进每;
2、 每次Reference類型數(shù)據(jù)寫操作時(shí)命斧,都會(huì)產(chǎn)生一個(gè)Write Barrier暫時(shí)中斷操作田晚;
3、然后檢查將要寫入的引用指向的對(duì)象是否和該Reference類型數(shù)據(jù)在不同的 Region(其他收集器:檢查老年代對(duì)象是否引用了新生代對(duì)象)国葬;
4贤徒、如果不同,通過(guò)CardTable把相關(guān)引用信息記錄到引用指向?qū)ο笏赗egion對(duì)應(yīng)的Remembered Set中汇四;
5接奈、進(jìn)行垃圾收集時(shí),在GC Roots的枚舉范圍加入Remembered Set船殉,就可以保證不進(jìn)行全局掃描鲫趁,也不會(huì)有遺漏。
G1收集器運(yùn)行過(guò)程
- 1利虫、初始標(biāo)記(Initial Marking)
僅標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象挨厚;并且修改TAMS(Next Top at Mark Start)堡僻,讓下一階段并發(fā)標(biāo)記時(shí),用戶程序能夠在正確可用的Region中創(chuàng)建新的對(duì)象疫剃。需要"Stop The World"钉疫,但速度很快; - 2 巢价、并發(fā)標(biāo)記(Concurrent Marking)
- 進(jìn)行GC Roots Tracing的過(guò)程牲阁;
- 剛才產(chǎn)生的集合中標(biāo)記出存活對(duì)象;
- 耗時(shí)較長(zhǎng)壤躲,但應(yīng)用程序也在運(yùn)行城菊;
- 并不能保證可以標(biāo)記出所有的存活對(duì)象;
- 3碉克、“最終標(biāo)記”(Final Marking)
為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記變動(dòng)的那一部分對(duì)象的標(biāo)記記錄凌唬; 上一階段對(duì)象的變化記錄在線程的Remembered Set Log;這里把Remembered Set Log合并到Remembered Set中漏麦;
需要"Stop The World"客税,且停頓時(shí)間比初始標(biāo)記稍長(zhǎng),但遠(yuǎn)比并發(fā)標(biāo)記短撕贞;采用多線程并行執(zhí)行來(lái)提升效率更耻; - 4、 篩選回收(Live Data Counting and Evacuation)
- 首先排序各個(gè)Region的回收價(jià)值和成本捏膨;
- 然后根據(jù)用戶期望的GC停頓時(shí)間來(lái)制定回收計(jì)劃秧均;
- 最后按計(jì)劃回收一些價(jià)值高的Region中垃圾對(duì)象;
回收時(shí)采用"復(fù)制"算法脊奋,從一個(gè)或多個(gè)Region復(fù)制存活對(duì)象到堆上的另一個(gè)空的Region熬北,并且在此過(guò)程中壓縮和釋放內(nèi)存;可以并發(fā)進(jìn)行诚隙,降低停頓時(shí)間讶隐,并增加吞吐量;
這里有一個(gè)疑問(wèn) 篩選回收時(shí)會(huì)Stop The World嘛久又?
深入理解JAVA虛擬機(jī)第二版中是會(huì)的巫延。
內(nèi)存分配與回收策略
對(duì)于Client模式下的JVM來(lái)說(shuō)(只有32位的JDK安裝才有Client模式的JVM,64位的JDK只有Server模式的JVM)地消,默認(rèn)的新生代和老年代的垃圾收集器是單線程的Serial(復(fù)制算法炉峰,并且存在擔(dān)保機(jī)制,默認(rèn)Eden區(qū)和Survivor是8:1)和Serial Old(標(biāo)記-整理算法)脉执。下面將研究疼阔,在這種組合的情況下,JVM的內(nèi)存分配和回收策略(ParNew和Serial的組合也差不多)。
- 對(duì)象優(yōu)先分配到新生代的Eden區(qū)
-
大對(duì)象直接進(jìn)入老年代:
所謂的大對(duì)象就是指需要大量連續(xù)內(nèi)存空間的JAVA對(duì)象婆廊,最典型的大對(duì)象就是那種很長(zhǎng)的字符串和數(shù)組迅细。經(jīng)常產(chǎn)生大對(duì)象容易導(dǎo)致額外的GC操作,JVM中提供了一個(gè)-XX:PretenureSizeThreshold參數(shù)(這個(gè)參數(shù)只對(duì)Serial和ParNew這兩個(gè)新生代垃圾收集器有效)淘邻,令大于這個(gè)參數(shù)的對(duì)象直接在老年代中分配茵典,這樣做的目的是避免在Eden區(qū)和兩個(gè)Survivor區(qū)之間發(fā)生大量的內(nèi)存拷貝。為什么宾舅?就在于Serial使用的是復(fù)制算法统阿。 -
長(zhǎng)期存活的對(duì)象將進(jìn)入老年代
JVM產(chǎn)生一個(gè)對(duì)象的時(shí)候,首先將其放在新生代的Eden區(qū)中,并且隨著mirror GC的產(chǎn)生,大部分的對(duì)象都被回收了愉耙,那么“熬過(guò)”這次GC的對(duì)象呢?JVM給了每個(gè)對(duì)象一個(gè)“年齡計(jì)數(shù)器”蜻直,所謂的年齡計(jì)數(shù)器就是指,這個(gè)對(duì)象熬過(guò)第一次GC袁串,并且進(jìn)入了Survivor區(qū)中,那么就將這個(gè)對(duì)象的年齡設(shè)為1呼巷,之后囱修,每熬過(guò)一次GC,年齡+1王悍,當(dāng)這個(gè)值到達(dá)一個(gè)閥值(默認(rèn)15破镰,可通過(guò)-XX:MaxTenuringThreshold來(lái)設(shè)置)時(shí),這個(gè)對(duì)象就會(huì)被移到老年代中压储。 -
動(dòng)態(tài)對(duì)象年齡判斷
為了更好適應(yīng)不同程序的內(nèi)存狀況鲜漩,JVM也不是要一個(gè)對(duì)象必須滿足MaxTenuringThreshold設(shè)置的年齡閥值才能進(jìn)入老年代。如果Survivor中的對(duì)象滿足同年齡(比如N)對(duì)象所占空間達(dá)到了Survivor總空間的一半的時(shí)候集惋,那么年齡大于或者等于N的對(duì)象都可以進(jìn)入老年代孕似,無(wú)需等待閥值。 -
空間分配擔(dān)保
新生代采用復(fù)制算法刮刑,會(huì)造成空間的浪費(fèi)喉祭,故而提出了一種“空間擔(dān)保機(jī)制”來(lái)提高復(fù)制算法的空間利用率,使復(fù)制算法的浪費(fèi)從50%降到了10%雷绢。而老年代的內(nèi)存就充當(dāng)了這個(gè)擔(dān)保者泛烙,并且由于沒(méi)有其他內(nèi)存來(lái)?yè)?dān)保老年代,所以老年代如果不想產(chǎn)生空間內(nèi)存碎片那么只能使用“標(biāo)記-整理”算法翘紊。
如何保證老年代有足夠的空間來(lái)執(zhí)行空間擔(dān)保機(jī)制呢蔽氨?Full GC,是否觸發(fā)根據(jù)經(jīng)驗(yàn)值判斷,即使不允許擔(dān)保失敗鹉究,也有可能發(fā)生擔(dān)保失敗宇立。
當(dāng)發(fā)生YGC的時(shí)候,JVM都會(huì)檢測(cè)之前每次晉升到老年代的對(duì)象的平均大小是否大于老年代的剩余內(nèi)存空間坊饶,如果大于泄伪,則觸發(fā)Full GC;如果小于匿级,則查看HandlePromotionFailure設(shè)置是否允許擔(dān)保失旙巍;如果允許痘绎,則不會(huì)觸發(fā)Full GC津函,反之,觸發(fā)Full GC孤页,保證老年代有足夠的空間支持空間分配擔(dān)保成功尔苦。
在每次GC發(fā)生的時(shí)候,我們也不知道到底會(huì)有多少對(duì)象被回收行施,又有多少對(duì)象能存活允坚。故而只好取之前每次回收晉升到老年代的對(duì)象的平均值作為經(jīng)驗(yàn)值來(lái)判斷,但是如果某次GC后存活對(duì)象激增蛾号,仍然會(huì)導(dǎo)致?lián)J〕硐睿敲粗荒苤匦逻M(jìn)行Full GC了,雖然這樣會(huì)繞個(gè)圈子鲜结,但是大部分情況下還是會(huì)將HandlePromotionFailure的值設(shè)為true展运,從而避免Full GC過(guò)于頻繁。換句話說(shuō)精刷,就是大部分情況拗胜,允許擔(dān)保失敗。