Java中的垃圾回收
翻譯原文 => plumbr Java GC handbook
前文參見(jiàn):
對(duì)標(biāo)記刪除垃圾回收算法的介紹更多還是偏理論性質(zhì)的响蕴。實(shí)踐中种蘸,為了更好地滿(mǎn)足現(xiàn)實(shí)的場(chǎng)景及需求巷折,還需要對(duì)算法進(jìn)行大量的調(diào)整星岗。舉個(gè)簡(jiǎn)單的例子,我們來(lái)看下JVM需要記錄哪些信息才能讓我們得以安全地繼續(xù)分配對(duì)象空間。
碎片和整理
無(wú)論何時(shí)進(jìn)行垃圾清除蜀变,JVM在清除不可達(dá)對(duì)象之后沟堡,還得確保它們所在的空間是可以被重新分配出去的侧但。對(duì)象刪除最終會(huì)導(dǎo)致碎片的出現(xiàn),這有點(diǎn)類(lèi)似于磁盤(pán)碎片航罗,從而會(huì)帶來(lái)兩個(gè)問(wèn)題:
- 寫(xiě)操作會(huì)變得更加費(fèi)時(shí)禀横,因?yàn)椴檎蚁乱粋€(gè)可用空閑塊已不再是一個(gè)簡(jiǎn)單操作。
- JVM在創(chuàng)建新對(duì)象的粥血,會(huì)在連續(xù)的區(qū)塊中分配內(nèi)存柏锄。因此如果碎片已經(jīng)嚴(yán)重到?jīng)]有一個(gè)單獨(dú)的連續(xù)空閑塊能夠容納新創(chuàng)建的對(duì)象時(shí),內(nèi)存分配便會(huì)報(bào)錯(cuò)复亏。
為了避免此類(lèi)情形趾娃,JVM需要確保碎片化在可控范圍內(nèi)。因此缔御,在垃圾回收的過(guò)程中抬闷,除了進(jìn)行標(biāo)記和刪除外,還有一個(gè)“內(nèi)存去碎片化”的過(guò)程耕突。在這個(gè)過(guò)程當(dāng)中饶氏,會(huì)給可達(dá)對(duì)象重新分配空間,讓它們互相緊挨著對(duì)方有勾,這樣便可以去除碎片。下圖展示的便是這一過(guò)程:
分代假設(shè)
如前所述古程,垃圾回收過(guò)程中需要完全中止應(yīng)用運(yùn)行蔼卡。顯然,對(duì)象越多挣磨,回收的時(shí)間也越長(zhǎng)雇逞。那么我們能不能在更小的內(nèi)存區(qū)域上進(jìn)行回收呢?通過(guò)可行性調(diào)查茁裙,一組研究人員發(fā)現(xiàn)應(yīng)用中絕大多數(shù)的內(nèi)存分配會(huì)分為兩大類(lèi):
- 絕大部分的對(duì)象很快會(huì)變?yōu)椴豢捎脿顟B(tài)塘砸。
- 還有一些,它們的存活時(shí)間通常也不會(huì)很長(zhǎng)晤锥。
這些觀察結(jié)論來(lái)自于弱分代假設(shè)(Weak Generational Hypothesis)掉蔬。基于這一假設(shè)矾瘾,虛擬機(jī)內(nèi)的內(nèi)存被分為兩類(lèi)女轿,新生代(Young Generation)及老生代(Old Generation)。后者有時(shí)也被稱(chēng)為年老代(Tenured Generation)壕翩。
有了各自獨(dú)立的可清除區(qū)域后蛉迹,這才有了針對(duì)各自不同區(qū)域的使用不同回收算法的可能性,正是它們一直以來(lái)在持續(xù)提升著GC的性能放妈。
這并不說(shuō)這樣的方式是沒(méi)有問(wèn)題的北救。比如說(shuō)荐操,不同分代中的對(duì)象可能彼此間有引用,在進(jìn)行分代回收時(shí)珍策,它們便為視為是“事實(shí)上”的GC根對(duì)象(GC roots)托启。
而更為重要的是,分代假設(shè)對(duì)于某些應(yīng)用來(lái)說(shuō)并不成立膛壹。由于GC算法主要是為那些要么“快速消失”要么“永久存活”的對(duì)象而進(jìn)行的優(yōu)化驾中,而對(duì)于那些生命周期不長(zhǎng)不短的對(duì)象來(lái)說(shuō),JVM的表現(xiàn)就非常差模聋。
內(nèi)存池
大家對(duì)下圖中關(guān)于堆的內(nèi)存池劃分應(yīng)該是非常熟悉的了肩民。不過(guò)大家可能不太清楚的是在不同的內(nèi)存池中,垃圾回收是如何履行它的職責(zé)的。值得注意的是废士,雖然不同的GC算法細(xì)節(jié)實(shí)現(xiàn)上有所不同朽缴,但是本章中所提到的概念卻是大同小異的。
伊甸區(qū)
新對(duì)象被創(chuàng)建時(shí)工窍,通常便會(huì)被分配到伊甸區(qū)。由于通常都會(huì)有多個(gè)線(xiàn)程在同時(shí)分配大量的對(duì)象前酿,因?yàn)橐恋閰^(qū)又被進(jìn)一步劃分成一個(gè)或多個(gè)線(xiàn)程本地分配緩沖(Thread Local Allocation Buffer患雏,簡(jiǎn)稱(chēng)TLAB)。有了這些緩沖區(qū)使得JVM中大多數(shù)對(duì)象的分配都可以在各個(gè)線(xiàn)程自己對(duì)應(yīng)的TLAB中完成罢维,從而避免了線(xiàn)程間昂貴的同步開(kāi)銷(xiāo)淹仑。
如果在TLAB中無(wú)法完成分配(通常是由于沒(méi)有足夠的空間),便會(huì)到伊甸區(qū)的共享空間中進(jìn)行分配肺孵。如果這里還是沒(méi)有足夠的空間匀借,則會(huì)觸發(fā)一次新生代垃圾回收的過(guò)程來(lái)釋放空間。如果垃圾回收后伊甸區(qū)還是沒(méi)有足夠的空間平窘,那么這個(gè)對(duì)象便會(huì)到老生代中去分配吓肋。
當(dāng)進(jìn)行伊甸區(qū)的回收時(shí),垃圾回收器會(huì)從根對(duì)象開(kāi)始遍歷所有的可達(dá)對(duì)象瑰艘,并將它們標(biāo)記為存活狀態(tài)是鬼。
前面我們已經(jīng)提到,對(duì)象間可能會(huì)存在跨代引用磅叛,因此最直觀的做法便是掃描其它分區(qū)到伊甸區(qū)的所有引用屑咳。是這么做就完全違背了分代的初衷。JVM對(duì)此有它自己的妙招:卡片式標(biāo)記(card-marking)弊琴≌琢基本的做法是,JVM將伊甸區(qū)中可能存在老生代引用的對(duì)象標(biāo)記為"臟”對(duì)象。關(guān)于這點(diǎn)Nitsan的博客這里有更進(jìn)一步的介紹紫皇。
標(biāo)記完成后慰安,所有存活對(duì)象會(huì)被復(fù)制到其中的一個(gè)存活區(qū)。于是整個(gè)伊甸區(qū)便可認(rèn)為是清空了聪铺,又可以重新用來(lái)分配對(duì)象了化焕。這一過(guò)程便被稱(chēng)為“標(biāo)記復(fù)制”:存活對(duì)象先被標(biāo)記,隨后被復(fù)制到存活區(qū)中铃剔。
存活區(qū)
緊挨著伊甸區(qū)的是兩個(gè)存活區(qū)撒桨,分別是from區(qū)和to區(qū)。值得一提的是其中的一個(gè)存活區(qū)始終都是空的键兜。
空的存活區(qū)會(huì)在下一次新生代GC的時(shí)候迎來(lái)它的居民凤类。整個(gè)新生代中的所有存活對(duì)象(包含伊甸區(qū)以及那個(gè)非空的名為from的存活區(qū))都會(huì)被復(fù)制到to區(qū)中。一旦完成之后普气,對(duì)象便都跑到to區(qū)中而from區(qū)則被清空了谜疤。這時(shí)兩者的角色便會(huì)發(fā)生調(diào)轉(zhuǎn)。
存活對(duì)象會(huì)不斷地在兩個(gè)存活區(qū)之間來(lái)回地復(fù)制现诀,直到其中的一些對(duì)象被認(rèn)為是已經(jīng)成熟夷磕,“足夠老”了。請(qǐng)記住這點(diǎn)仔沿,基于分代假設(shè)坐桩,已經(jīng)存活了一段時(shí)間的對(duì)象,在相當(dāng)長(zhǎng)的一段時(shí)間內(nèi)仍可能繼續(xù)存活封锉。
這些“年老”的對(duì)象會(huì)被提升至老年代空間撕攒。出現(xiàn)對(duì)象提升的時(shí)候,這些對(duì)象則不會(huì)再被復(fù)制到另一個(gè)存活區(qū)烘浦,而是直接復(fù)制到老年代中,它們會(huì)一直待到不再被引用為止萍鲸。
垃圾回收器會(huì)跟蹤每個(gè)對(duì)象歷經(jīng)的回收次數(shù)闷叉,來(lái)判斷它們是否已經(jīng)“足夠年老”,可以傳播至老年代中脊阴。在一輪GC完成之后握侧,每個(gè)分區(qū)中存活下來(lái)的對(duì)象的年齡便會(huì)加一。當(dāng)一個(gè)對(duì)象的年齡超過(guò)了一個(gè)特定的年老閾值之后嘿期,它便會(huì)被提升到老年代中品擎。
JVM會(huì)動(dòng)態(tài)地調(diào)整實(shí)際的年齡閾值,不過(guò)通過(guò)指定-XX:+MaxTenuringThreshold參數(shù)可以給該值設(shè)置一個(gè)上限备徐。將-XX:+MaxTenuringThreshold設(shè)置為0則立即觸發(fā)對(duì)象提升萄传,而不會(huì)復(fù)制到存活區(qū)中。在現(xiàn)代的JVM中蜜猾,這個(gè)值默認(rèn)會(huì)被設(shè)置為15個(gè)GC周期秀菱。在HotSpot虛擬機(jī)中這也是該值的上限振诬。
如果存活區(qū)的大小不足以存放所有的新生代存活對(duì)象,則會(huì)出現(xiàn)過(guò)早提升衍菱。
老生代
老生代的內(nèi)存空間的實(shí)現(xiàn)則更為復(fù)雜赶么。老生代的空間通常都會(huì)非常大,里面存放的對(duì)象都是不太可能需要被回收的脊串。
老生代的GC比新生代的GC發(fā)生的頻率要少得多辫呻。由于老生代中的多數(shù)對(duì)象都被認(rèn)為是存活的,所以在GC中不會(huì)采用標(biāo)記-復(fù)制算法琼锋,而是把這些對(duì)象挪到一起以減少碎片放闺。老生代的回收算法通常都是根據(jù)不同的理論來(lái)構(gòu)建的。不過(guò)大體上都會(huì)分成如下幾步:
- 標(biāo)記可達(dá)對(duì)象斩例,設(shè)置通過(guò)GC根對(duì)象可達(dá)的所有對(duì)象的標(biāo)記位
- 刪除不可達(dá)對(duì)象
- 整理老生代空間的對(duì)象雄人,將存活對(duì)象復(fù)制到老生代開(kāi)始的連續(xù)空間內(nèi)。
從以上描述中可知念赶,為了避免過(guò)度碎片化础钠,老生代的GC是明確需要進(jìn)行整理操作的。
持久代
在Java 8以前還有一個(gè)特殊的空間叫做持久代(Permanent Generation)叉谜。這是元數(shù)據(jù)比如類(lèi)相關(guān)數(shù)據(jù)存放的地方旗吁。除此之外,像駐留的字符串(internalized string)也會(huì)被存放在持久代中停局。這的確給Java開(kāi)發(fā)人員帶來(lái)了不少麻煩事很钓,因?yàn)楹茈y評(píng)估這究竟會(huì)使用到多少空間。評(píng)估不到位偏會(huì)拋出java.lang.OutOfMemoryError: Permgen space的異常董栽。只要不是真的因?yàn)閮?nèi)存泄漏而引起的OutOfMemoryError異常码倦,可以通過(guò)增加持久代空間的大小來(lái)解決這一問(wèn)題,比如下例中的把持久代最大空間設(shè)置為256MB:
java -XX:MaxPermSize=256m com.mycompany.MyApplication
元空間
由于元數(shù)據(jù)空間大小的預(yù)測(cè)是件繁瑣且低效的工作锭碳,于是Java 8中干脆就去掉了持久代袁稽,轉(zhuǎn)而推出了元空間。從此以后擒抛,那些個(gè)雜七雜八的東西便都存儲(chǔ)到正常的Java堆了推汽。
但是,類(lèi)定義如今則是存儲(chǔ)到了元空間里歧沪。它存儲(chǔ)在本地內(nèi)存中歹撒,不會(huì)與堆 內(nèi)存相混雜。默認(rèn)情況下诊胞,元空間的大小只受限于Java進(jìn)程的可用本地內(nèi)存的大小暖夭。這大大解放了開(kāi)發(fā)人員,他們不會(huì)再因?yàn)槎嘣黾恿艘粋€(gè)類(lèi)而引發(fā)java.lang.OutOfMemoryError: Permgen space異常了。值得注意的是鳞尔,雖然看似元空間大小毫無(wú)限制了嬉橙,但這一些并非是沒(méi)有代價(jià)的——如果任由元空間無(wú)節(jié)制地增長(zhǎng),你可能會(huì)面臨的是頻繁的內(nèi)存交換(swapping)或者是本地內(nèi)存分配失敗寥假。
如果你希望避免此類(lèi)情況市框,可以像下例中這樣限制一下元空間的大小,將它設(shè)置成比如256MB:
java -XX:MaxMetaspaceSize=256m com.mycompany.MyApplication
Minor GC vs Major GC vs Full GC
清除堆內(nèi)存不同區(qū)域的垃圾回收事件又被稱(chēng)為Minor GC,Major GC糕韧,以及Full GC事件枫振。本章我們將介紹一下不同事件的區(qū)別在哪里。不過(guò)你會(huì)發(fā)現(xiàn)其實(shí)各自的差別并不是那么重要萤彩。
重要的是我們希望知道應(yīng)用是否到達(dá)它的SLA了粪滤,而這又只能去監(jiān)控應(yīng)用的處理延時(shí)或者吞吐量。只有在這個(gè)時(shí)候討論GC事件才有意義雀扶。這些事件的關(guān)鍵之處在于它們是否停止了應(yīng)用的運(yùn)行杖小,以及停了多久。
不過(guò)由于Minor GC, Major GC愚墓,F(xiàn)ull GC這幾個(gè)術(shù)語(yǔ)被廣泛使用卻又沒(méi)有一個(gè)清晰的定義予权,我們還是先來(lái)詳細(xì)地介紹一下它們的區(qū)別再說(shuō)吧。
Minor GC
新生代垃圾的回收被稱(chēng)作Minor GC浪册。這個(gè)定義非常清晰扫腺,理解起來(lái)也不會(huì)有什么歧義。不過(guò)當(dāng)處理新生代GC事件時(shí)村象,還是有一些有意思的東西值得注意的:
- 只要JVM無(wú)法為新創(chuàng)建的對(duì)象分配空間笆环,就肯定會(huì)觸發(fā)新生代GC,比方說(shuō)Eden區(qū)滿(mǎn)了厚者。因此對(duì)象創(chuàng)建得越頻繁躁劣,新生代GC肯定也更頻繁。
- 在一次新生代GC事件中库菲,通常不涉及到年老代习绢。年老代到年輕代的引用被認(rèn)為是GC的根對(duì)象。而在標(biāo)記階段中蝙昙,從年輕代到年老代的引用則會(huì)被忽略掉。
- 和通常所理解的不一樣的是梧却,所有的新生代GC都會(huì)觸發(fā)“stop-the-world”暫停奇颠,這會(huì)中斷應(yīng)用程序的線(xiàn)程。對(duì)絕大多數(shù)應(yīng)用而言放航,如果Eden區(qū)中的大多數(shù)對(duì)象都是垃圾對(duì)象并且永遠(yuǎn)不會(huì)被拷貝到Survivor區(qū)/年老代中的話(huà),暫停的時(shí)間是可以忽略不計(jì)的烈拒。如果恰好相反的話(huà),那么絕大多數(shù)的新生對(duì)象都不應(yīng)該被回收,新生代GC的暫停時(shí)間就會(huì)變得相對(duì)較長(zhǎng)了荆几。
現(xiàn)在來(lái)看新生代GC還是很清晰的——每一次新生代GC都會(huì)對(duì)年輕代進(jìn)行垃圾清除吓妆。
Major GC vs Full GC
你會(huì)發(fā)現(xiàn)關(guān)于這兩種GC其實(shí)并沒(méi)有明確的定義。JVM規(guī)范或者垃圾回收相關(guān)的論文中都沒(méi)有提及吨铸。不過(guò)從直覺(jué)來(lái)說(shuō)行拢,基于Minor GC是清理新生代空間的認(rèn)識(shí)來(lái)看,不難得出以下推論:
- Major GC是清理老年代的空間诞吱。
- Full GC是清理整個(gè)堆——包括新生代與老年代空間
不幸的是這么理解會(huì)有一點(diǎn)復(fù)雜與困惑舟奠。首先許多老年代GC其實(shí)是由新生代GC觸發(fā)的,因此在很多情況下兩者無(wú)法孤立來(lái)看待房维。另一方面許多現(xiàn)代的垃圾回收器會(huì)對(duì)老年代進(jìn)行部分清理沼瘫,因此,使用“清理”這個(gè)術(shù)語(yǔ)則顯得有點(diǎn)牽強(qiáng)咙俩。
那么問(wèn)題就來(lái)了耿戚,先別再糾結(jié)某次GC到底是Major GC還是Full GC了,你更應(yīng)該關(guān)注的是這次GC是否中斷了應(yīng)用線(xiàn)程還是能夠和應(yīng)用線(xiàn)程并發(fā)地執(zhí)行阿趁。
即便是在JVM的官方工具中膜蛔,也存在著這一困擾。通過(guò)一個(gè)例子來(lái)說(shuō)明應(yīng)該更容易理解一些歌焦。我們用兩款工具來(lái)跟蹤某個(gè)運(yùn)行著CMS回收器的JVM飞几,來(lái)比較下它們的輸出有什么不同:
首先通過(guò)jstat的輸出來(lái)查看下GC的信息:
my-precious: me$ jstat -gc -t 4235 1s
Time S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
5.7 34048.0 34048.0 0.0 34048.0 272640.0 194699.7 1756416.0 181419.9 18304.0 17865.1 2688.0 2497.6 3 0.275 0 0.000 0.275
6.7 34048.0 34048.0 34048.0 0.0 272640.0 247555.4 1756416.0 263447.9 18816.0 18123.3 2688.0 2523.1 4 0.359 0 0.000 0.359
7.7 34048.0 34048.0 0.0 34048.0 272640.0 257729.3 1756416.0 345109.8 19072.0 18396.6 2688.0 2550.3 5 0.451 0 0.000 0.451
8.7 34048.0 34048.0 34048.0 34048.0 272640.0 272640.0 1756416.0 444982.5 19456.0 18681.3 2816.0 2575.8 7 0.550 0 0.000 0.550
9.7 34048.0 34048.0 34046.7 0.0 272640.0 16777.0 1756416.0 587906.3 20096.0 19235.1 2944.0 2631.8 8 0.720 0 0.000 0.720
10.7 34048.0 34048.0 0.0 34046.2 272640.0 80171.6 1756416.0 664913.4 20352.0 19495.9 2944.0 2657.4 9 0.810 0 0.000 0.810
11.7 34048.0 34048.0 34048.0 0.0 272640.0 129480.8 1756416.0 745100.2 20608.0 19704.5 2944.0 2678.4 10 0.896 0 0.000 0.896
12.7 34048.0 34048.0 0.0 34046.6 272640.0 164070.7 1756416.0 822073.7 20992.0 19937.1 3072.0 2702.8 11 0.978 0 0.000 0.978
13.7 34048.0 34048.0 34048.0 0.0 272640.0 211949.9 1756416.0 897364.4 21248.0 20179.6 3072.0 2728.1 12 1.087 1 0.004 1.091
14.7 34048.0 34048.0 0.0 34047.1 272640.0 245801.5 1756416.0 597362.6 21504.0 20390.6 3072.0 2750.3 13 1.183 2 0.050 1.233
15.7 34048.0 34048.0 0.0 34048.0 272640.0 21474.1 1756416.0 757347.0 22012.0 20792.0 3200.0 2791.0 15 1.336 2 0.050 1.386
16.7 34048.0 34048.0 34047.0 0.0 272640.0 48378.0 1756416.0 838594.4 22268.0 21003.5 3200.0 2813.2 16 1.433 2 0.050 1.484
這段輸出是從JVM啟動(dòng)后第17秒開(kāi)始截取的。從中可以看出独撇,在經(jīng)過(guò)了12次新生代GC后出現(xiàn)了兩次Full GC屑墨,共耗時(shí)50ms。通過(guò)GUI的工具也可以獲取到同樣的信息纷铣,比如說(shuō)jsonsole或者是jvisualvm卵史。
在接受這一結(jié)論前,我們?cè)賮?lái)看下同樣是這次JVM啟動(dòng)后所輸出的GC日志搜立。很明顯-XX:+PrintGCDetails給我們講述的是一段截然不同卻更為詳盡的故事:
java -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC eu.plumbr.demo.GarbageProducer
3.157: [GC (Allocation Failure) 3.157: [ParNew: 272640K->34048K(306688K), 0.0844702 secs] 272640K->69574K(2063104K), 0.0845560 secs] [Times: user=0.23 sys=0.03, real=0.09 secs]
4.092: [GC (Allocation Failure) 4.092: [ParNew: 306688K->34048K(306688K), 0.1013723 secs] 342214K->136584K(2063104K), 0.1014307 secs] [Times: user=0.25 sys=0.05, real=0.10 secs]
... cut for brevity ...
11.292: [GC (Allocation Failure) 11.292: [ParNew: 306686K->34048K(306688K), 0.0857219 secs] 971599K->779148K(2063104K), 0.0857875 secs] [Times: user=0.26 sys=0.04, real=0.09 secs]
12.140: [GC (Allocation Failure) 12.140: [ParNew: 306688K->34046K(306688K), 0.0821774 secs] 1051788K->856120K(2063104K), 0.0822400 secs] [Times: user=0.25 sys=0.03, real=0.08 secs]
12.989: [GC (Allocation Failure) 12.989: [ParNew: 306686K->34048K(306688K), 0.1086667 secs] 1128760K->931412K(2063104K), 0.1087416 secs] [Times: user=0.24 sys=0.04, real=0.11 secs]
13.098: [GC (CMS Initial Mark) [1 CMS-initial-mark: 897364K(1756416K)] 936667K(2063104K), 0.0041705 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
13.102: [CMS-concurrent-mark-start]
13.341: [CMS-concurrent-mark: 0.238/0.238 secs] [Times: user=0.36 sys=0.01, real=0.24 secs]
13.341: [CMS-concurrent-preclean-start]
13.350: [CMS-concurrent-preclean: 0.009/0.009 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
13.350: [CMS-concurrent-abortable-preclean-start]
13.878: [GC (Allocation Failure) 13.878: [ParNew: 306688K->34047K(306688K), 0.0960456 secs] 1204052K->1010638K(2063104K), 0.0961542 secs] [Times: user=0.29 sys=0.04, real=0.09 secs]
14.366: [CMS-concurrent-abortable-preclean: 0.917/1.016 secs] [Times: user=2.22 sys=0.07, real=1.01 secs]
14.366: [GC (CMS Final Remark) [YG occupancy: 182593 K (306688 K)]14.366: [Rescan (parallel) , 0.0291598 secs]14.395: [weak refs processing, 0.0000232 secs]14.395: [class unloading, 0.0117661 secs]14.407: [scrub symbol table, 0.0015323 secs]
14.409: [scrub string table, 0.0003221 secs][1 CMS-remark: 976591K(1756416K)] 1159184K(2063104K), 0.0462010 secs] [Times: user=0.14 sys=0.00, real=0.05 secs]
14.412: [CMS-concurrent-sweep-start]
14.633: [CMS-concurrent-sweep: 0.221/0.221 secs] [Times: user=0.37 sys=0.00, real=0.22 secs]
14.633: [CMS-concurrent-reset-start]
14.636: [CMS-concurrent-reset: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
從以上能夠看出以躯,在運(yùn)行了12次新生代GC后的確出現(xiàn)了一些“不太尋常”的事情啄踊。但并不是執(zhí)行了兩次Full GC忧设,這個(gè)“不尋常”的事情其實(shí)只是在年老代中執(zhí)行了一次包含了數(shù)個(gè)階段的GC而已:
- 初始標(biāo)記階段颠通,從0.0041705 秒或者說(shuō)4ms的時(shí)候開(kāi)始址晕。這一階段是一次“stop-the-world”事件,所有的應(yīng)用線(xiàn)程都被暫停以便進(jìn)行初始標(biāo)記顿锰。
- 并發(fā)地執(zhí)行標(biāo)記和預(yù)清理(Preclean)的階段谨垃。這是和應(yīng)用線(xiàn)程一起并發(fā)執(zhí)行的启搂。
- 最終標(biāo)記階段,從0.0462010秒或者說(shuō)46毫秒的時(shí)候開(kāi)始刘陶。這一階段也同樣是“stop-the-world”的胳赌。
- 并發(fā)地進(jìn)行清除操作。正如名字所說(shuō)的匙隔,這一階段也無(wú)需中斷應(yīng)用線(xiàn)程疑苫,可以并發(fā)地執(zhí)行。
因此我們從實(shí)際的GC日志中所看到的是這樣——其實(shí)沒(méi)有什么兩次所謂的Full GC,只有一次清理年老代空間的Major GC而已牡直。
如果你再看下jstat輸出的結(jié)果缀匕,就不難得出結(jié)論了。它確切地指出了兩次stop-the-world事件碰逸,總耗時(shí)50ms乡小,這段時(shí)間內(nèi)所有活躍線(xiàn)程都會(huì)出現(xiàn)延遲響應(yīng)。不過(guò)如果你想據(jù)此來(lái)優(yōu)化吞吐量的話(huà)饵史,很可能會(huì)徒勞無(wú)功——jstat只列出了兩次stop-the-world的初始標(biāo)記及最終標(biāo)記的部分满钟,而并發(fā)執(zhí)行的那部分工作卻被它給隱藏掉了。