垃圾回收算法具體實(shí)現(xiàn)
翻譯原文 => plumbr Java GC handbook
前文參見:
Java垃圾回收手冊(一):初識垃圾回收
Java垃圾回收手冊(二):Java中的垃圾回收
Java垃圾回收手冊(三):垃圾回收算法基礎(chǔ)
在熟悉GC算法背后的核心概念之后凝垛,我們來看看JVM提供的各種GC算法的具體實(shí)現(xiàn)项贺。對于大部分JVM來說漏策,GC算法主要分為兩類歉甚,分別針對年輕代和老生代棠众。
你可以選擇JVM提供的各種GC算法炒嘲。如果不顯示指定的話轴或,JVM會(huì)根據(jù)具體運(yùn)行平臺選擇默認(rèn)算法的。這篇文章我們來詳細(xì)探討一下這些算法的工作原理频蛔。
為了更直觀一些灵迫,這里先列出來各種GC算法的可能組合(這些組合只針對Java 8,其他版本可能稍有不同)晦溪。
年輕代 | 老生代 | JVM參數(shù) |
---|---|---|
Incremental | Incremental | -Xincgc |
Serial | Serial | -XX:+UseSerialGC |
Parallel Scavenge | Serial | -XX:+UseParallelGC -XX:-UseParallelOldGC |
Parallel New | Serial | N/A |
Serial | Parallel Old | N/A |
Parallel Scavenge | Parallel Old | -XX:+UseParallelGC -XX:+UseParallelOldGC |
Parallel New | Parallel Old | N/A |
Serial | CMS | -XX:-UseParNewGC -XX:+UseConcMarkSweepGC |
Parallel Scavenge | CMS | N/A |
Parallel New | CMS | -XX:+UseParNewGC -XX:+UseConcMarkSweepGC |
G1 | -XX:+UseG1GC |
如果覺得這個(gè)表格看起來比較復(fù)雜瀑粥,先不用擔(dān)心。實(shí)際上常用的只有圖中用粗線標(biāo)出的4種組合三圆,剩下要么已經(jīng)被棄用狞换,要么不再被支持或者說在實(shí)際應(yīng)用環(huán)境中很少使用。所以我們接下來只討論這幾種組合的工作原理嫌术。
- Serial GC(年輕代+老生代)
- Parallel GC(年輕代+老生代)
- Parallel New(年輕代) + CMS(老生代)
- G1(本身不區(qū)分年輕代和老生代)
Serial GC
這類垃圾回收器針對年輕代使用標(biāo)記-拷貝算法哀澈,針對老生代使用標(biāo)記-清除-整理算法牌借。顧名思義度气,這些收集器是單線程,無法并行執(zhí)行操作膨报。他們也會(huì)產(chǎn)生讓所有應(yīng)用進(jìn)程都停止的stop-the-world停頓磷籍。
這種垃圾收集器無法利用現(xiàn)代非常普遍的多核CPU适荣,無論CPU有幾個(gè)核,在JVM的垃圾回收過程中只能使用一個(gè)核院领。
使用以下參數(shù)可以針對年輕代和老生代開啟該垃圾收集器:
java -XX:+UseSerialGC com.mypackages.MyExecutableClass
該設(shè)置只有在CPU是單核弛矛,可用內(nèi)存只有幾百兆的JVM運(yùn)行環(huán)境情況下才推薦使用。對于大部分的服務(wù)端部署來講比然,很少使用這個(gè)組合丈氓。大部分服務(wù)端部署都是基于多核平臺,選擇Serial GC就人為限制了資源的使用强法,這肯定會(huì)導(dǎo)致資源浪費(fèi)万俗,而這些資源本來可以用來降低延遲或者提高吞吐量。
我們現(xiàn)在來看看在使用Serial GC算法時(shí)的GC日志格式饮怯,看從中我們能獲得哪些信息闰歪。為此,我們需要打開JVM的GC日志功能蓖墅。
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps
此時(shí)的GC日志格式如下:
2015-05-26T14:45:37.987-0200: 151.126: [GC (Allocation Failure) 151.126: [DefNew: 629119K->69888K(629120K), 0.0584157 secs] 1619346K->1273247K(2027264K), 0.0585007 secs] [Times: user=0.06 sys=0.00, real=0.06 secs]
2015-05-26T14:45:59.690-0200: 172.829: [GC (Allocation Failure) 172.829: [DefNew: 629120K->629120K(629120K), 0.0000372 secs]172.829: [Tenured: 1203359K->755802K(1398144K), 0.1855567 secs] 1832479K->755802K(2027264K), [Metaspace: 6741K->6741K(1056768K)], 0.1856954 secs] [Times: user=0.18 sys=0.00, real=0.18 secs]
GC日志片段暴露了很多信息库倘,告訴我們JVM內(nèi)部正在發(fā)生什么。事實(shí)上论矾,這段日志表明有兩個(gè)GC事件發(fā)生了:一個(gè)是針對年輕代的GC教翩,一個(gè)是針對整個(gè)堆的。我們逐個(gè)來具體分析一下日志的具體含義拇囊。
Minor GC
2015-05-26T14:45:37.987-02001:151.1262:[GC3(Allocation Failure4) 151.126: [DefNew5:629119K->69888K6(629120K)7, 0.0584157 secs]1619346K->1273247K8(2027264K)9,0.0585007 secs10][Times: user=0.06 sys=0.00, real=0.06 secs]11
- 2015-05-26T14:45:37.987-0200:GC開始時(shí)間
- 151.126:GC開始時(shí)間迂曲,相對JVM啟動(dòng)時(shí)間,單位是秒
- GC:區(qū)分是Minor GC還是Full GC寥袭,這里表示Minor GC
- Allocation Failure:產(chǎn)生GC的原因路捧,這里是因?yàn)闊o法在年輕代為某個(gè)數(shù)據(jù)結(jié)構(gòu)分配空間導(dǎo)致觸發(fā)GC
- DefNew:收集器名字:一個(gè)單線程,使用標(biāo)記-拷貝算法传黄,會(huì)產(chǎn)生的STW的垃圾收集器
- 629119K->69888K:GC前后年輕代使用情況
- (629120K):年輕代總?cè)萘?/li>
- 1619346K->1273247K:GC前后堆使用情況
- (2027264K):堆總?cè)萘?/li>
- 0.0585007 secs:GC事件持續(xù)時(shí)間杰扫,單位是秒
- [Times: user=0.06 sys=0.00, real=0.06 secs]:GC事件的時(shí)間開銷,分三個(gè)類別:
- user:整個(gè)過程GC耗費(fèi)的全部CPU時(shí)間 => 垃圾收集器消耗的所有CPU執(zhí)行時(shí)間之和膘掰,所謂的CPU time
- sys:系統(tǒng)耗費(fèi)時(shí)間章姓,包括系統(tǒng)調(diào)用或者等待系統(tǒng)事件
- real:應(yīng)用程序因GC被停止的時(shí)間。因?yàn)镾erial GC是單線程的识埋,real time等于user time + sys time => 所謂的wall clock time凡伊,字面意思,墻上時(shí)間窒舟,該過程時(shí)鐘走過的時(shí)間
從上面的日志片段我們非常清楚的知道在GC發(fā)生過程中系忙,JVM里面各個(gè)內(nèi)存空間的使用情況。GC之前惠豺,堆內(nèi)存總共使用了1619346K银还,這其中年輕代內(nèi)存使用了629119K风宁,從而可以推算出老生代的內(nèi)存使用量為990,227K。
我們通過簡單的計(jì)算還可以從這里面了解到更重要的信息:GC之后蛹疯,年輕代內(nèi)存使用量減少559,231K戒财,但是整個(gè)堆的內(nèi)存使用量只減少346,099K,從而我們可以推算出捺弦,在該GC過程中饮寞,有213,132K大小的對象從年輕代晉升到老生代。
用圖來展示GC前和GC后內(nèi)存使用變化如下:
Full GC
2015-05-26T14:45:59.690-02001: 172.8292:[GC (Allocation Failure) 172.829: [DefNew: 629120K->629120K(629120K), 0.0000372 secs3]172.829:[Tenured4: 1203359K->755802K5(1398144K)6,0.1855567 secs7] 1832479K->755802K8(2027264K)9,[Metaspace: 6741K->6741K(1056768K)]10 [Times: user=0.18 sys=0.00, real=0.18 secs]11
- 2015-05-26T14:45:59.690-0200:GC開始時(shí)間
- 172.829:GC開始時(shí)間列吼,相對JVM啟動(dòng)時(shí)間骂际,單位是秒
- [DefNew: 629120K->629120K(629120K), 0.0000372 secs:和上面的例子類似,因?yàn)閮?nèi)存分配失敗導(dǎo)致了一次年輕代的GC冈欢,同樣是一個(gè)叫做DefNew的收集器被運(yùn)行了歉铝,GC后,年輕代內(nèi)存使用從629120K降到0凑耻。這里日志里顯示GC后依舊是629120K是JVM的bug
- Tenured:老生代垃圾收集器名字:一個(gè)單線程太示,使用標(biāo)記-清除-整理算法,會(huì)產(chǎn)生STW的垃圾回收器
- 1203359K->755802K:GC前后老生代使用情況
- (1398144K):老生代總?cè)萘?/li>
- 0.1855567 secs:老生代GC所耗時(shí)間
- 1832479K->755802K:GC(年輕代+老生代)前后堆使用情況
- (2027264K):堆總?cè)萘?/li>
- [Metaspace: 6741K->6741K(1056768K)]:關(guān)于元空間的類似信息
- [Times: user=0.18 sys=0.00, real=0.18 secs]:GC事件的時(shí)間開銷香浩,分三個(gè)類別:
- user:整個(gè)過程GC耗費(fèi)的全部CPU時(shí)間
- sys:系統(tǒng)耗費(fèi)時(shí)間类缤,包括系統(tǒng)調(diào)用或者等待系統(tǒng)事件
- real:應(yīng)用程序因GC被停止的時(shí)間。因?yàn)镾erial GC是單線程的邻吭,real time等于user time + sys time
這個(gè)跟Minor GC的區(qū)別非常明顯:除了年輕代餐弱,在這次GC過程中,老生代和元空間也被清除了囱晴。
用圖來展示GC前和GC后內(nèi)存使用變化如下:
Parallel GC
這個(gè)組合包括在年輕代使用標(biāo)記-復(fù)制算法的GC膏蚓,在老生代使用標(biāo)記-清除-整理算法的GC。年輕代和老生代的GC都會(huì)導(dǎo)致STW事件產(chǎn)生畸写,所有應(yīng)用在GC過程中都必須停下來驮瞧。但在標(biāo)記和復(fù)制/整理階段是使用多線程,這也就是為什么稱之為“并行”GC枯芬。使用這個(gè)算法论笔,可以大大減少GC時(shí)間。
該垃圾收集器使用的線程數(shù)可以通過參數(shù) -XX:ParallelGCThreads=NNN 來設(shè)置千所,默認(rèn)為系統(tǒng)CPU核數(shù)狂魔。
使用以下參數(shù)之一可以開啟該GC算法:
java -XX:+UseParallelGC com.mypackages.MyExecutableClass
java -XX:+UseParallelOldGC com.mypackages.MyExecutableClass
java -XX:+UseParallelGC -XX:+UseParallelOldGC com.mypackages.MyExecutableClass
Parallel GC適合多核機(jī)器,并且你的主要目標(biāo)是提高吞吐量淫痰。更高的吞吐量得益于更高效的使用系統(tǒng)資源最楷。
- GC期間,所有核并行清除垃圾,從而可以獲得更短的停頓時(shí)間
- 在GC周期之間管嬉,不消耗任何系統(tǒng)資源
另外,因?yàn)樗械氖占A段仍然是不允許被打斷的朗鸠,這種垃圾收集器仍有可能導(dǎo)致長時(shí)間停頓的發(fā)生蚯撩。如果你的主要目標(biāo)是延遲的話,你應(yīng)該考慮下一節(jié)介紹的CMS烛占。
我們現(xiàn)在來看看在使用Parallel GC算法時(shí)的GC日志格式胎挎,看從中我們能獲得哪些信息。同樣我們截取兩個(gè)日志片段忆家,一個(gè)是Minor GC的犹菇,一個(gè)是Major GC的。
2015-05-26T14:27:40.915-0200: 116.115: [GC (Allocation Failure) [PSYoungGen: 2694440K->1305132K(2796544K)] 9556775K->8438926K(11185152K), 0.2406675 secs] [Times: user=1.77 sys=0.01, real=0.24 secs]
2015-05-26T14:27:41.155-0200: 116.356: [Full GC (Ergonomics) [PSYoungGen: 1305132K->0K(2796544K)] [ParOldGen: 7133794K->6597672K(8388608K)] 8438926K->6597672K(11185152K), [Metaspace: 6745K->6745K(1056768K)], 0.9158801 secs] [Times: user=4.49 sys=0.64, real=0.92 secs]
Minor GC
2015-05-26T14:27:40.915-02001: 116.1152:[GC3(Allocation Failure4)[PSYoungGen5: 2694440K->1305132K6(2796544K)7]9556775K->8438926K8(11185152K)9, 0.2406675 secs10][Times: user=1.77 sys=0.01, real=0.24 secs]11
- 2015-05-26T14:27:40.915-0200:GC開始時(shí)間
- 116.115:GC開始時(shí)間芽卿,相對JVM啟動(dòng)時(shí)間揭芍,單位是秒
- GC:區(qū)分是Minor GC還是Full GC,這里表示Minor
- Allocation Failure:產(chǎn)生GC的原因卸例,這里是因?yàn)闊o法在年輕代為某個(gè)數(shù)據(jù)結(jié)構(gòu)分配空間導(dǎo)致觸發(fā)GC
- PSYoungGen:年輕代垃圾收集器名字:一個(gè)并行的称杨,使用標(biāo)記-拷貝算法,會(huì)產(chǎn)生STW的垃圾回收器
- 2694440K->1305132K:GC前后年輕代使用情況
- (2796544K):年輕代總?cè)萘?/li>
- 9556775K->8438926K:GC前后堆使用情況
- (11185152K):堆總?cè)萘?/li>
- 0.2406675 secs:GC事件持續(xù)時(shí)間筷转,單位是秒
- [Times: user=1.77 sys=0.01, real=0.24 secs]:GC事件的時(shí)間開銷姑原,分三個(gè)類別:
- user:整個(gè)過程GC耗費(fèi)的全部CPU時(shí)間
- sys:系統(tǒng)耗費(fèi)時(shí)間,包括系統(tǒng)調(diào)用或者等待系統(tǒng)事件
- real:應(yīng)用程序因GC被停止的時(shí)間呜舒。對于Parallel GC來說锭汛,real time接近于(user time + sys time)/GC線程數(shù),這里使用了8個(gè)線程袭蝗。因?yàn)槟承┗顒?dòng)不能并行唤殴,所以這個(gè)值會(huì)稍稍大一點(diǎn)。
簡單來說到腥,GC之前眨八,堆使用量是9,556,775K,其中年輕代使用量是2,694,440K左电,可知老生代使用量為6,862,335K廉侧。GC之后,年輕代的使用量減少了1,389,308K篓足,而整個(gè)堆的使用量只減少1,117,849K段誊,可知該過程中有271,459K對象從年輕代晉升到老生代。如圖:
Full GC
2015-05-26T14:27:41.155-02001:116.3562:[Full GC3 (Ergonomics4)[PSYoungGen: 1305132K->0K(2796544K)]5[ParOldGen6:7133794K->6597672K7(8388608K)8] 8438926K->6597672K9(11185152K)10, [Metaspace: 6745K->6745K(1056768K)] 11, 0.9158801 secs12, [Times: user=4.49 sys=0.64, real=0.92 secs]13
- 2015-05-26T14:27:41.155-0200:GC開始時(shí)間
- 116.356:GC開始時(shí)間栈拖,相對JVM啟動(dòng)時(shí)間连舍,單位是秒
- Full GC:表示這是Full GC,垃圾清理包括年輕代和老生代
- Ergonomics:產(chǎn)生GC的原因涩哟,這里表示JVM的內(nèi)部工效邏輯判斷目前是進(jìn)行垃圾回收的好時(shí)機(jī)
- [PSYoungGen: 1305132K->0K(2796544K)]:跟上面的類似索赏,在年輕代使用了一個(gè)并行的盼玄,使用標(biāo)記-拷貝算法,會(huì)產(chǎn)生STW的PSYoungGen垃圾回收器潜腻,回收之后年輕代被清空埃儿,這也是一次Full GC的典型結(jié)果
- ParOldGen:老生代使用的垃圾收集器名字:一個(gè)并行的,使用標(biāo)記-清除-整理算法融涣,會(huì)產(chǎn)生STW的垃圾回收器
- 7133794K->6597672K:GC前后老生代使用情況
- (8388608K):老生代總?cè)萘?/li>
- 8438926K->6597672K:GC前后堆使用情況
- (11185152K):堆總?cè)萘?/li>
- [Metaspace: 6745K->6745K(1056768K)]:關(guān)于元空間的類似信息
- 0.9158801 secs:GC事件持續(xù)時(shí)間童番,單位是秒
- [Times: user=4.49 sys=0.64, real=0.92 secs]:GC事件的時(shí)間開銷,分三個(gè)類別:
- user:整個(gè)過程GC耗費(fèi)的全部CPU時(shí)間
- sys:系統(tǒng)耗費(fèi)時(shí)間威鹿,包括系統(tǒng)調(diào)用或者等待系統(tǒng)事件
- real:應(yīng)用程序因GC被停止的時(shí)間剃斧。對于Parallel GC來說,real time接近于(user time + sys time)/GC線程數(shù)忽你,這里使用了8個(gè)線程幼东。因?yàn)槟承┗顒?dòng)不能并行,所以這個(gè)值會(huì)稍稍大一點(diǎn)科雳。
同樣筋粗,這個(gè)跟Minor GC的區(qū)別非常明顯:除了年輕代,在這次GC過程中炸渡,老生代和元空間也被清除了娜亿。
用圖來展示GC前和GC后內(nèi)存使用變化如下:
CMS
該垃圾收集器組合的官方名字是“Mostly Concurrent Mark and Sweep Garbage Collector”。它在年輕代使用并行的蚌堵,使用標(biāo)記-拷貝算法买决,會(huì)產(chǎn)生STW的GC,在老生代使用并發(fā)的吼畏,使用標(biāo)記-清除算法的GC督赤。
這個(gè)垃圾收集器設(shè)計(jì)初衷是在老生代垃圾收集中避免長時(shí)間停頓。它通過兩個(gè)手段來實(shí)現(xiàn)該目標(biāo)泻蚊。一:不對老生代內(nèi)存進(jìn)行整理操作躲舌,而是使用空閑列表來管理那些可回收再利用的內(nèi)存空間。二:在標(biāo)記-清除階段性雄,和應(yīng)用程序并發(fā)的做掉絕大分工作没卸。這意味著垃圾回收并不會(huì)顯示停止應(yīng)用程序線程來執(zhí)行這些操作。盡管如此秒旋,垃圾回收器線程還是會(huì)跟應(yīng)用程序線程搶占CPU時(shí)間约计。默認(rèn)情況下,這種GC算法使用的線程數(shù)量等于機(jī)器CPU核數(shù)的1/4迁筛。
可用通過以下參數(shù)啟用該垃圾回收器:
java -XX:+UseConcMarkSweepGC com.mypackages.MyExecutableClass
如果應(yīng)用程序運(yùn)行在多核機(jī)器上煤蚌,且你的主要目標(biāo)是降低延遲,CMS會(huì)是一個(gè)不錯(cuò)的選擇。降低單次GC停頓時(shí)間直接影響終端用戶對應(yīng)用程序的感受尉桩,會(huì)讓用戶感覺應(yīng)用程序響應(yīng)更快筒占。由于在大部分時(shí)間里,總有一些CPU資源被GC使用而不是用來執(zhí)行你的應(yīng)用程序代碼蜘犁,所以對于計(jì)算密集型應(yīng)用來說翰苫,CMS在吞吐量上不如Parallel GC。
和之前的GC算法一樣沽瘦,讓我們再次通過查看包含一次Minor GC和一次Major GC的GC日志來看看該算法是如何在實(shí)際中應(yīng)用的。
2015-05-26T16:23:07.219-0200: 64.322: [GC (Allocation Failure) 64.322: [ParNew: 613404K->68068K(613440K), 0.1020465 secs] 10885349K->10880154K(12514816K), 0.1021309 secs] [Times: user=0.78 sys=0.01, real=0.11 secs]
2015-05-26T16:23:07.321-0200: 64.425: [GC (CMS Initial Mark) [1 CMS-initial-mark: 10812086K(11901376K)] 10887844K(12514816K), 0.0001997 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2015-05-26T16:23:07.321-0200: 64.425: [CMS-concurrent-mark-start]
2015-05-26T16:23:07.357-0200: 64.460: [CMS-concurrent-mark: 0.035/0.035 secs] [Times: user=0.07 sys=0.00, real=0.03 secs]
2015-05-26T16:23:07.357-0200: 64.460: [CMS-concurrent-preclean-start]
2015-05-26T16:23:07.373-0200: 64.476: [CMS-concurrent-preclean: 0.016/0.016 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
2015-05-26T16:23:07.373-0200: 64.476: [CMS-concurrent-abortable-preclean-start]
2015-05-26T16:23:08.446-0200: 65.550: [CMS-concurrent-abortable-preclean: 0.167/1.074 secs] [Times: user=0.20 sys=0.00, real=1.07 secs]
2015-05-26T16:23:08.447-0200: 65.550: [GC (CMS Final Remark) [YG occupancy: 387920 K (613440 K)]65.550: [Rescan (parallel) , 0.0085125 secs]65.559: [weak refs processing, 0.0000243 secs]65.559: [class unloading, 0.0013120 secs]65.560: [scrub symbol table, 0.0008345 secs]65.561: [scrub string table, 0.0001759 secs][1 CMS-remark: 10812086K(11901376K)] 11200006K(12514816K), 0.0110730 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
2015-05-26T16:23:08.458-0200: 65.561: [CMS-concurrent-sweep-start]
2015-05-26T16:23:08.485-0200: 65.588: [CMS-concurrent-sweep: 0.027/0.027 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
2015-05-26T16:23:08.485-0200: 65.589: [CMS-concurrent-reset-start]
2015-05-26T16:23:08.497-0200: 65.601: [CMS-concurrent-reset: 0.012/0.012 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Minor GC
2015-05-26T16:23:07.219-02001: 64.3222:[GC3(Allocation Failure4) 64.322: [ParNew5: 613404K->68068K6(613440K) 7, 0.1020465 secs8] 10885349K->10880154K 9(12514816K)10, 0.1021309 secs11][Times: user=0.78 sys=0.01, real=0.11 secs]12
- 2015-05-26T16:23:07.219-0200:GC開始時(shí)間
- 64.322:GC開始時(shí)間农尖,相對JVM啟動(dòng)時(shí)間析恋,單位是秒
- GC:區(qū)分是Minor GC還是Full GC,這里表示Minor
- Allocation Failure:產(chǎn)生GC的原因盛卡,這里是因?yàn)闊o法在年輕代為某個(gè)數(shù)據(jù)結(jié)構(gòu)分配空間導(dǎo)致觸發(fā)GC
- ParNew:年輕代垃圾收集器名字:一個(gè)并行的助隧,使用標(biāo)記-拷貝算法,會(huì)產(chǎn)生STW的垃圾回收器滑沧,該垃圾收集器是用來跟老生代的CMS配合使用的
- 613404K->68068K:GC前后年輕代使用情況
- (613440K):年輕代總?cè)萘?/li>
- 0.1020465 secs:GC事件持續(xù)時(shí)間
- 10885349K->10880154K:GC前后堆使用情況
- (12514816K):堆總?cè)萘?/li>
- 0.1021309 secs:GC標(biāo)記和拷貝年輕代里面活對象耗費(fèi)的時(shí)間并村,這里面包括和老生代CMS通訊開銷,對象晉升到老年代的開銷滓技,GC結(jié)束前清理工作的開銷
- [Times: user=0.78 sys=0.01, real=0.11 secs]:GC事件的時(shí)間開銷哩牍,分三個(gè)類別:
- user:整個(gè)過程GC耗費(fèi)的全部CPU時(shí)間
- sys:系統(tǒng)耗費(fèi)時(shí)間,包括系統(tǒng)調(diào)用或者等待系統(tǒng)事件
- real:應(yīng)用程序因GC被停止的時(shí)間令漂。對于Parallel GC來說膝昆,real time接近于(user time + sys time)/GC線程數(shù),這里使用了8個(gè)線程叠必。因?yàn)槟承┗顒?dòng)不能并行荚孵,所以這個(gè)值會(huì)稍稍大一點(diǎn)。
從上面可以看出纬朝,GC之前收叶,堆使用量是10,885,349K,其中年輕代使用量是613,404K共苛,可知老生代使用量為10,271,945K判没。GC之后,年輕代的使用量減少了545,336K隅茎,而整個(gè)堆的使用量只減少5,195K哆致,可知該過程中有540,141K對象從年輕代晉升到老生代。如圖:
Full GC
當(dāng)你已經(jīng)開始熟悉垃圾收集器的日志格式的時(shí)候患膛,這一節(jié)將介紹一個(gè)格式完全不同的日志格式摊阀。下面的這個(gè)日志輸出包含了CMS日志的所有階段,為了更好的解釋清楚,我們按照階段來逐個(gè)分析各個(gè)階段的日志含義胞此。
2015-05-26T16:23:07.321-0200: 64.425: [GC (CMS Initial Mark) [1 CMS-initial-mark: 10812086K(11901376K)] 10887844K(12514816K), 0.0001997 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2015-05-26T16:23:07.321-0200: 64.425: [CMS-concurrent-mark-start]
2015-05-26T16:23:07.357-0200: 64.460: [CMS-concurrent-mark: 0.035/0.035 secs] [Times: user=0.07 sys=0.00, real=0.03 secs]
2015-05-26T16:23:07.357-0200: 64.460: [CMS-concurrent-preclean-start]
2015-05-26T16:23:07.373-0200: 64.476: [CMS-concurrent-preclean: 0.016/0.016 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
2015-05-26T16:23:07.373-0200: 64.476: [CMS-concurrent-abortable-preclean-start]
2015-05-26T16:23:08.446-0200: 65.550: [CMS-concurrent-abortable-preclean: 0.167/1.074 secs] [Times: user=0.20 sys=0.00, real=1.07 secs]
2015-05-26T16:23:08.447-0200: 65.550: [GC (CMS Final Remark) [YG occupancy: 387920 K (613440 K)]65.550: [Rescan (parallel) , 0.0085125 secs]65.559: [weak refs processing, 0.0000243 secs]65.559: [class unloading, 0.0013120 secs]65.560: [scrub symbol table, 0.0008345 secs]65.561: [scrub string table, 0.0001759 secs][1 CMS-remark: 10812086K(11901376K)] 11200006K(12514816K), 0.0110730 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
2015-05-26T16:23:08.458-0200: 65.561: [CMS-concurrent-sweep-start]
2015-05-26T16:23:08.485-0200: 65.588: [CMS-concurrent-sweep: 0.027/0.027 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
2015-05-26T16:23:08.485-0200: 65.589: [CMS-concurrent-reset-start]
2015-05-26T16:23:08.497-0200: 65.601: [CMS-concurrent-reset: 0.012/0.012 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
請記住一點(diǎn):在真實(shí)世界里臣咖,在對老生代進(jìn)行并發(fā)垃圾回收的同時(shí),年輕代的GC隨時(shí)可能發(fā)生漱牵。這個(gè)時(shí)候夺蛇,Minor GC和Full GC的日志就會(huì)穿插的出現(xiàn)在GC日志文件里面。
Phase 1: Initial Mark酣胀。 這是CMS GC過程中兩次STW中的一次刁赦。這個(gè)階段的目標(biāo)是標(biāo)記出老生代里面符合條件的對象,這些對象或者是從GC roots直接指向的闻镶,或者被年輕代活著對象指向的甚脉。后者非常重要,因?yàn)槔仙菃为?dú)回收的铆农。
2015-05-26T16:23:07.321-0200: 64.421: [GC (CMS Initial Mark2[1 CMS-initial-mark: 10812086K3(11901376K)4] 10887844K5(12514816K)6, 0.0001997 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]7
- 2015-05-26T16:23:07.321-0200: 64.42:GC開始時(shí)間
- CMS Initial Mark:該階段的名字
- 10812086K:當(dāng)前老生代使用量
- (11901376K):老生代總?cè)萘?/li>
- 10887844K:當(dāng)前堆使用量
- (12514816K):堆總?cè)萘?/li>
- [Times: user=0.00 sys=0.00, real=0.00 secs]:該階段的耗時(shí)
Phase 2: Concurrent Mark牺氨。 在這個(gè)階段,垃圾收集器從上一個(gè)階段找到的所有根節(jié)點(diǎn)開始遍歷整個(gè)老生代墩剖,標(biāo)記所有活著的對象猴凹。這個(gè)階段是和應(yīng)用程序并發(fā)執(zhí)行的,不會(huì)停止應(yīng)用程序進(jìn)程岭皂。這里需要注意的是郊霎,因?yàn)槭遣l(fā)執(zhí)行,程序可能在標(biāo)記過程中修改引用爷绘,所以并不是所有的活著都會(huì)被標(biāo)記出歹篓。
如圖所示,在標(biāo)記的過程中揉阎,“Current obj”指向另一個(gè)對象的引用被刪除了庄撮。
2015-05-26T16:23:07.321-0200: 64.425: [CMS-concurrent-mark-start]
2015-05-26T16:23:07.357-0200: 64.460: [CMS-concurrent-mark1: 035/0.035 secs2] [Times: user=0.07 sys=0.00, real=0.03 secs]3
- CMS-concurrent-mark:該階段的名字
- 035/0.035 secs:該階段消耗的時(shí)間,分別展示了clock時(shí)間和cpu時(shí)間
- [Times: user=0.07 sys=0.00, real=0.03 secs]:并發(fā)階段的時(shí)間字段參考意義不大
Phase 3: Concurrent Preclean毙籽。 這又是一個(gè)并發(fā)階段洞斯,和應(yīng)用程序并發(fā)執(zhí)行的,不會(huì)停止應(yīng)用程序進(jìn)程坑赡。在前一個(gè)階段和應(yīng)用程序并發(fā)執(zhí)行的過程中烙如,一些引用可能被修改,當(dāng)修改發(fā)生時(shí)毅否,JVM都會(huì)把包含該修改對象的堆區(qū)域(又被稱為卡片)標(biāo)記為“臟數(shù)據(jù)”(又被稱為卡片標(biāo)記)
在這個(gè)階段亚铁,從臟數(shù)據(jù)到達(dá)的對象也會(huì)被標(biāo)記成活對象,標(biāo)記完成之后螟加,卡片臟數(shù)據(jù)標(biāo)記會(huì)被清除徘溢。
該階段還會(huì)為Final Remark階段做一些必要的整理記錄和準(zhǔn)備的工作吞琐。
2015-05-26T16:23:07.357-0200: 64.460: [CMS-concurrent-preclean-start]
2015-05-26T16:23:07.373-0200: 64.476: [CMS-concurrent-preclean1: 0.016/0.016 secs2] [Times: user=0.02 sys=0.00, real=0.02 secs]3
- CMS-concurrent-preclean:該階段的名字
- 0.016/0.016 secs:該階段消耗的時(shí)間,分別展示了clock時(shí)間和cpu時(shí)間
- [Times: user=0.02 sys=0.00, real=0.02 secs]:并發(fā)階段的時(shí)間字段參考意義不大
Phase 4: Concurrent Abortable Preclean然爆。 還是一個(gè)并發(fā)階段站粟,不會(huì)停止應(yīng)用程序進(jìn)程。這個(gè)階段嘗試著去承擔(dān)STW的Final Remark階段足夠多的工作曾雕。這個(gè)階段持續(xù)的時(shí)間依賴很多的因素奴烙,由于這個(gè)階段是重復(fù)的做相同的事情直到某些中止條件被滿足為止(中止條件包括:重復(fù)的次數(shù)、多少量的工作剖张、累計(jì)執(zhí)行時(shí)間等等)切诀。
2015-05-26T16:23:07.373-0200: 64.476: [CMS-concurrent-abortable-preclean-start]
2015-05-26T16:23:08.446-0200: 65.550: [CMS-concurrent-abortable-preclean1: 0.167/1.074 secs2] [Times: user=0.20 sys=0.00, real=1.07 secs]3
- CMS-concurrent-abortable-preclean:該階段的名字
- 0.167/1.074 secs:該階段消耗的時(shí)間,分別展示了clock時(shí)間和cpu時(shí)間搔弄。這里會(huì)發(fā)現(xiàn)user time要比clock time小得多幅虑。一般我們都是看到real time要比user time小,這是因?yàn)橛行┕ぷ鞅徊l(fā)執(zhí)行了肯污,所以elapsed clock time要比使用的CPU time少翘单。這里我們僅有少量的工作吨枉,0.167秒的CPU時(shí)間蹦渣,垃圾回收器線程花費(fèi)了很多時(shí)間在等待上。也就是說貌亭,他們在盡量推遲STW的到來柬唯,默認(rèn)情況下,這個(gè)階段可能持續(xù)5秒
- [Times: user=0.20 sys=0.00, real=1.07 secs]:并發(fā)階段的時(shí)間字段參考意義不大
這個(gè)階段可能對即將到來的STW階段影響非常大圃庭,有很多重要的配置參數(shù)和失敗方式锄奢。
Phase 5: Final Remark。 這是第二個(gè)也是最后一個(gè)STW階段剧腻。這個(gè)階段的目標(biāo)就是最終標(biāo)記老生代的所有活著的對象拘央。因?yàn)橹暗膒reclean是并發(fā)階段,他們可能無法趕上應(yīng)用程序的修改速度书在。所以需要一個(gè)STW暫停來完成整個(gè)標(biāo)記過程灰伟。
通常CMS試著在年輕代盡可能空的情況下執(zhí)行最終標(biāo)記,試圖減少多個(gè)STW階段一個(gè)接著一個(gè)的產(chǎn)生的可能性儒旬。
這個(gè)階段的GC日志比前面階段要復(fù)雜一些栏账。
2015-05-26T16:23:08.447-0200: 65.5501: [GC (CMS Final Remark2) [YG occupancy: 387920 K (613440 K)3]65.550: [Rescan (parallel) , 0.0085125 secs]465.559: [weak refs processing, 0.0000243 secs]65.5595: [class unloading, 0.0013120 secs]65.5606: [scrub string table, 0.0001759 secs7][1 CMS-remark: 10812086K(11901376K)8] 11200006K(12514816K) 9, 0.0110730 secs10] [Times: user=0.06 sys=0.00, real=0.01 secs]11
- 2015-05-26T16:23:08.447-0200: 65.550:GC開始時(shí)間
- CMS Final Remark:該階段的名字
- YG occupancy: 387920 K (613440 K):當(dāng)前年輕代的使用量和總?cè)萘?/li>
- [Rescan (parallel) , 0.0085125 secs]:應(yīng)用程序停止過程中標(biāo)記所有活著對象所耗的時(shí)間
- [weak refs processing, 0.0000243 secs]65.559:第一個(gè)子階段:處理弱引用所耗時(shí)間
- [class unloading, 0.0013120 secs]65.560:第二個(gè)子階段:卸載不用類所耗時(shí)間
- [scrub string table, 0.0001759 secs:最后一個(gè)子階段:清除字符表(存儲類級別元數(shù)據(jù))和字符串表(存儲駐留字符串)所耗時(shí)間,停頓的clock time也被包括在里面
- 10812086K(11901376K):標(biāo)記完之后老生代使用量和總?cè)萘?/li>
- 11200006K(12514816K):標(biāo)記完之后堆使用量和總?cè)萘?/li>
- 0.0110730 secs:該階段所耗時(shí)間
- [Times: user=0.06 sys=0.00, real=0.01 secs]:GC事件的時(shí)間開銷栈源,分三個(gè)類別
在經(jīng)歷五個(gè)標(biāo)記階段之后挡爵,老生代所有活著的對象都被標(biāo)記完畢,現(xiàn)在垃圾收集器就準(zhǔn)備通過清除老生代來回收所有不使用對象的存儲空間甚垦。
Phase 6: Concurrent Sweep茶鹃。 該階段也是跟應(yīng)用程序并發(fā)執(zhí)行涣雕,不需要STW停頓。這個(gè)階段的目標(biāo)是清除那些不再使用的對象前计,并回收他們占用的內(nèi)存空間以備后續(xù)使用胞谭。
2015-05-26T16:23:08.458-0200: 65.561: [CMS-concurrent-sweep-start] 2015-05-26T16:23:08.485-0200: 65.588: [CMS-concurrent-sweep1: 0.027/0.027 secs2] [Times: user=0.03 sys=0.00, real=0.03 secs] 3
- CMS-concurrent-sweep:該階段名字
- 0.027/0.027 secs:該階段消耗的時(shí)間,分別展示了clock時(shí)間和cpu時(shí)間男杈。
- [Times: user=0.03 sys=0.00, real=0.03 secs]:并發(fā)階段的時(shí)間字段參考意義不大
Phase 7: Concurrent Reset丈屹。 并發(fā)執(zhí)行階段,重置CMS算法的內(nèi)部數(shù)據(jù)結(jié)構(gòu)伶棒,為下一次執(zhí)行做準(zhǔn)備旺垒。
2015-05-26T16:23:08.485-0200: 65.589: [CMS-concurrent-reset-start] 2015-05-26T16:23:08.497-0200: 65.601: [CMS-concurrent-reset1: 0.012/0.012 secs2] [Times: user=0.01 sys=0.00, real=0.01 secs]3
- CMS-concurrent-reset:該階段名字
- 0.012/0.012 secs:該階段消耗的時(shí)間,分別展示了clock時(shí)間和cpu時(shí)間肤无。
- [Times: user=0.01 sys=0.00, real=0.01 secs]:并發(fā)階段的時(shí)間字段參考意義不大
總的來講先蒋,CMS垃圾回收器通過把大量的工作放到不需要應(yīng)用程序停頓的并發(fā)線程里面去做掉,大大減少了應(yīng)用程序的停頓時(shí)間宛渐。但是竞漾,它也有自己的缺點(diǎn),最明顯的就是老生代內(nèi)存碎片問題和某些情況下停頓時(shí)間的不可預(yù)測性窥翩,特別是在大內(nèi)存堆的情況下
G1
G1的一個(gè)重要的設(shè)計(jì)目標(biāo)就是由GC導(dǎo)致的STW停頓的持續(xù)時(shí)間可預(yù)測业岁,持續(xù)時(shí)間長短可配置。實(shí)際上寇蚊,G1是一種軟實(shí)時(shí)垃圾收集器笔时,這意味著你可以給它設(shè)定具體的性能指標(biāo)。你可以要求在任意給定Y毫秒范圍內(nèi)STW停頓持續(xù)時(shí)間不超過X毫秒仗岸,比如:在任意一秒鐘內(nèi)不超過5毫秒允耿。G1垃圾收集器會(huì)盡自己最大努力盡可能滿足這個(gè)目標(biāo)設(shè)定(這個(gè)目標(biāo)不一定能達(dá)成,否則就是硬實(shí)時(shí)了)扒怖。
為了達(dá)成這個(gè)設(shè)計(jì)目標(biāo)较锡,G1提供了幾個(gè)新思路。首先盗痒,堆不是被劃分成連續(xù)的年輕代和老生代蚂蕴,而是被劃分成一些(典型是2048個(gè))更小的堆區(qū)域塊,這些區(qū)域塊用來存儲對象积糯。任意一個(gè)區(qū)域塊可能是一個(gè)伊甸區(qū)掂墓,也可能是一個(gè)存活區(qū),還可能是一個(gè)老年區(qū)看成。邏輯上把所有的伊甸區(qū)和存活區(qū)合起來稱作年輕代君编,所有的老年區(qū)合起來稱作老生代。
這樣的話川慌,GC可以采用增量的方式吃嘿,每次回收一些區(qū)域塊祠乃,而不是對整個(gè)堆進(jìn)行垃圾回收。在每次停頓時(shí)兑燥,會(huì)對所有年輕代區(qū)域進(jìn)行垃圾回收亮瓷,某些老生代區(qū)域可能也被包含進(jìn)來一起。
其次降瞳,G1在并發(fā)階段它會(huì)計(jì)算每個(gè)區(qū)域塊包含多少活對象嘱支。這個(gè)用來構(gòu)建單次收集集合:包含最多垃圾的區(qū)域塊最先進(jìn)行垃圾收集。這也是它的名字由來:garbage-first挣饥。
啟用G1垃圾收集器:
java -XX:+UseG1GC com.mypackages.MyExecutableClass
Evacuation Pause:全年輕代模式
在應(yīng)用程序生命周期初期除师,G1還未執(zhí)行并發(fā)標(biāo)記階段,對堆內(nèi)存無任何額外信息扔枫。所以它最開始是運(yùn)行于全年輕代模式汛聚。當(dāng)整個(gè)年輕代被對象裝滿,應(yīng)用程序線程被停止了短荐,年輕代的活對象被拷貝到存活區(qū)倚舀,或者被拷貝到任何空閑區(qū)域塊,這些空閑區(qū)域塊也就變成了存活區(qū)塊忍宋。
這個(gè)拷貝過程被稱作Evacuation痕貌,它的工作方式跟之前介紹的年輕代垃圾收集器類似。整個(gè)evacuation pause階段的日志非常大讶踪,為了簡單化芯侥,我們把和全年輕代模式evacuation pause無關(guān)的日志省略掉了泊交。在對并行階段進(jìn)行詳細(xì)介紹之后我們再來解釋這些被省略的日志乳讥。另外,考慮到日志記錄的大小廓俭,這里把并行階段和其他階段的詳細(xì)日志都剝離到獨(dú)立的部分云石。
0.134: [GC pause (G1 Evacuation Pause) (young), 0.0144119 secs]1
[Parallel Time: 13.9 ms, GC Workers: 8]2
…3
[Code Root Fixup: 0.0 ms]4
[Code Root Purge: 0.0 ms]5
[Clear CT: 0.1 ms]
[Other: 0.4 ms]6
…7
[Eden: 24.0M(24.0M)->0.0B(13.0M) 8Survivors: 0.0B->3072.0K 9Heap: 24.0M(256.0M)->21.9M(256.0M)]10
[Times: user=0.04 sys=0.04, real=0.02 secs] 11
- 0.134: [GC pause (G1 Evacuation Pause) (young), 0.0144119 secs]:evacuation pause開始于JVM啟動(dòng)之后0.134秒,持續(xù)了0.0144119秒
- [Parallel Time: 13.9 ms, GC Workers: 8]:下列活動(dòng)用8個(gè)并發(fā)工作線程執(zhí)行研乒,總共花了13.9ms(clock time)
- 這里為了簡化汹忠,省略了具體的活動(dòng)內(nèi)容,具體見后面章節(jié)
- [Code Root Fixup: 0.0 ms]:釋放用來管理并行活動(dòng)的數(shù)據(jù)結(jié)構(gòu)雹熬,這個(gè)值一般都接近于0宽菜。這個(gè)操作是順序的
- [Code Root Purge: 0.0 ms]:清除更多的數(shù)據(jù)結(jié)構(gòu),這個(gè)操作也非掣捅ǎ快铅乡,但不一定是0。這個(gè)操作也是順序的
- [Other: 0.4 ms]:其他各種活動(dòng)耗時(shí)烈菌,很多都是并行的
- 具體見后面章節(jié)
- [Eden: 24.0M(24.0M)->0.0B(13.0M):執(zhí)行前后伊甸區(qū)的使用量和總?cè)萘?/li>
- Survivors: 0.0B->3072.0K:執(zhí)行前后存活區(qū)的使用量
- Heap: 24.0M(256.0M)->21.9M(256.0M)]:執(zhí)行前后堆的使用量和總?cè)萘?/li>
- [Times: user=0.04 sys=0.04, real=0.02 secs]:GC事件的時(shí)間開銷阵幸,分三個(gè)類別
- user:整個(gè)過程GC耗費(fèi)的全部CPU時(shí)間
- sys:系統(tǒng)耗費(fèi)時(shí)間花履,包括系統(tǒng)調(diào)用或者等待系統(tǒng)事件
- real:應(yīng)用程序因GC被停止的時(shí)間。由于GC的活動(dòng)是并行的挚赊,real time接近于(user time + sys time)/GC線程數(shù)诡壁,這里使用了8個(gè)線程。因?yàn)槟承┗顒?dòng)不能并行荠割,所以這個(gè)值會(huì)稍稍大一點(diǎn)妹卿。
多個(gè)專屬GC工作線程負(fù)責(zé)執(zhí)行大部分耗時(shí)操作,具體內(nèi)容如下:
[Parallel Time: 13.9 ms, GC Workers: 8]1
[GC Worker Start (ms)2: Min: 134.0, Avg: 134.1, Max: 134.1, Diff: 0.1]
[Ext Root Scanning (ms)<sup>3</sup>: Min: 0.1, Avg: 0.2, Max: 0.3, Diff: 0.2, Sum: 1.2]
[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Code Root Scanning (ms)<sup>4</sup>: Min: 0.0, Avg: 0.0, Max: 0.2, Diff: 0.2, Sum: 0.2]
[Object Copy (ms)<sup>5</sup>: Min: 10.8, Avg: 12.1, Max: 12.6, Diff: 1.9, Sum: 96.5]
[Termination (ms)<sup>6</sup>: Min: 0.8, Avg: 1.5, Max: 2.8, Diff: 1.9, Sum: 12.2]
[Termination Attempts<sup>7</sup>: Min: 173, Avg: 293.2, Max: 362, Diff: 189, Sum: 2346]
[GC Worker Other (ms)<sup>8</sup>: Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
GC Worker Total (ms)<sup>9</sup>: Min: 13.7, Avg: 13.8, Max: 13.8, Diff: 0.1, Sum: 110.2]
[GC Worker End (ms)<sup>10</sup>: Min: 147.8, Avg: 147.8, Max: 147.8, Diff: 0.0]
- [Parallel Time: 13.9 ms, GC Workers: 8]:下列活動(dòng)用8個(gè)并發(fā)工作線程執(zhí)行蔑鹦,總共花了13.9ms(clock time)
- [GC Worker Start (ms):工作線程開始執(zhí)行操作的時(shí)間影晓,應(yīng)該和停頓的開始時(shí)間一致含衔。如果最小和最大差距太大,可能意味著使用了太多線程,或者機(jī)器上有其他進(jìn)程和JVM的GC進(jìn)程爭搶CPU資源
- [Ext Root Scanning (ms):掃描非堆的GC roots所耗時(shí)間锋叨,包括類加載器,JNI引用棱诱,JVM系統(tǒng)根等藻雪,除了sum是cpu time,其他都是clock time
- [Code Root Scanning (ms):掃描來自實(shí)際代碼的GC roots魁索,比如本地變量等
- [Object Copy (ms):從收集區(qū)域塊拷貝活對象所耗時(shí)間
- [Termination (ms):工作線程確信所有工作已經(jīng)做完融撞,可以安全停止所耗時(shí)間
- [Termination Attempts:工作線程嘗試中止的次數(shù),如果還有未完成的工作粗蔚,就是一次失敗的中止嘗試
- [GC Worker Other (ms):其他各種活動(dòng)耗時(shí)
- GC Worker Total (ms):GC工作線程所耗時(shí)間總和
- [GC Worker End (ms):工作線程完成工作的時(shí)間尝偎,通常應(yīng)該時(shí)間差不多,否則意味著太多線程堵塞或者有其他進(jìn)程爭搶CPU資源
另外鹏控,在該階段還有一些其他活動(dòng)在執(zhí)行致扯。這里只介紹部分內(nèi)容,其他的放在后面章節(jié)当辐。
[Other: 0.4 ms]1
[Choose CSet: 0.0 ms]
[Ref Proc: 0.2 ms]2
[Ref Enq: 0.0 ms]3
[Redirty Cards: 0.1 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.0 ms]4
- [Other: 0.4 ms]:其他各種活動(dòng)耗時(shí)抖僵,很多都是并行的
- [Ref Proc: 0.2 ms]:處理非強(qiáng)引用耗時(shí):決定是清除它們還是無視它們
- [Ref Enq: 0.0 ms]:待處理的非強(qiáng)引用入對應(yīng)隊(duì)列所耗時(shí)間
- [Free CSet: 0.0 ms]:返回收集集合中已經(jīng)釋放區(qū)域塊所耗時(shí)間
并發(fā)標(biāo)記
G1垃圾收集器借鑒了CMS的很多理念。所以在繼續(xù)之前確保你對CMS的這些概念有很好的理解缘揪。盡管G1和CMS有很多不同耍群,但是并發(fā)標(biāo)記階段的目的非常類似。G1并發(fā)標(biāo)記使用SATB(Snapshot-At-The-Beginning)的方法找筝,對所有在標(biāo)記開始那一刻活著的對象進(jìn)行標(biāo)記蹈垢,即使后來它變成垃圾。根據(jù)這些信息可以構(gòu)建每個(gè)區(qū)域塊的活對象狀態(tài)信息袖裕,這樣就可以很高效的確定收集集合曹抬。
這個(gè)信息也用于老生代的GC。如果通過標(biāo)記發(fā)現(xiàn)某個(gè)區(qū)域塊只包含垃圾陆赋,或者在對老生代(既包含垃圾也包含活對象)做evacuation pause的STW期間沐祷,并發(fā)標(biāo)記可以完全并發(fā)執(zhí)行嚷闭。
當(dāng)堆內(nèi)存使用空間足夠大就會(huì)觸發(fā)并發(fā)標(biāo)記。默認(rèn)是45%赖临,這個(gè)值可以通過InitiatingHeapOccupancyPercent進(jìn)行調(diào)整胞锰。和CMS類似,G1中的并發(fā)標(biāo)記也包含很多階段兢榨,這些階段有些是完全并發(fā)嗅榕,有些需要停止應(yīng)用程序。
Phase 1: Initial Mark吵聪。 標(biāo)記所有可以從GC roots直達(dá)的對象凌那。在CMS里,這個(gè)階段需要單獨(dú)的STW吟逝,但是在G1里帽蝶,它只是evacuation pause的附帶停頓,所以它的開銷很小块攒。在日志里面的表現(xiàn)就是在evacuation pause日志首行帶有Initial Mark字樣励稳。
1.631: [GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0062656 secs]
Phase 2: Root Region Scan。 標(biāo)記所有可以從所謂的根區(qū)域塊可達(dá)的活對象囱井。例如驹尼,那些不是空。因?yàn)樵诓l(fā)標(biāo)記過程中移動(dòng)對象會(huì)有問題庞呕,這個(gè)過程必須在下一次evacuation pause開始之前完成新翎。如果它要提前開始,就需要提前中止root region scan住练,并等到它結(jié)束地啰。在目前的實(shí)現(xiàn)中,根區(qū)域塊是存活區(qū):他們是年輕代里面的對象澎羞,必定在下一次evacuation pause中被回收髓绽。
1.362: [GC concurrent-root-region-scan-start]
1.364: [GC concurrent-root-region-scan-end, 0.0028513 secs]
Phase 3:Concurrent Mark敛苇。 跟CMS類似:遍歷對象圖妆绞,在一個(gè)特殊的位圖里面標(biāo)記訪問的對象。為了確保滿足SATB的要求枫攀,G1要求應(yīng)用程序?qū)ο髨D的并發(fā)更新要留下用來標(biāo)記的舊引用括饶。這是通過Pre-Write barriers實(shí)現(xiàn)的(注意不要跟下文提到的Post-Write barriers以及多線程編程里面的memory barriers混淆概念):在G1并發(fā)標(biāo)記執(zhí)行過程中,對任何對象的寫入来涨,把舊引用存儲到日志緩存里面图焰,這些內(nèi)容到時(shí)候會(huì)被并發(fā)標(biāo)記線程處理的。
1.364: [GC concurrent-mark-start]
1.645: [GC concurrent-mark-end, 0.2803470 secs]
Phase 4:Remark蹦掐。 這個(gè)跟CMS的最終標(biāo)記類似技羔,需要STW停頓僵闯,用來終結(jié)整個(gè)標(biāo)記階段。G1停止應(yīng)用程序藤滥,不讓它們繼續(xù)產(chǎn)生并發(fā)更新日志鳖粟,然后處理完剩下的日志,標(biāo)記所有在并發(fā)標(biāo)記開始時(shí)刻是活著的所有未標(biāo)記對象拙绊。該階段還會(huì)處理一些額外的清除操作向图,比如引用處理(請參照前面的evacuation pause日志)或者類卸載。
1.645: [GC remark 1.645: [Finalize Marking, 0.0009461 secs] 1.646: [GC ref-proc, 0.0000417 secs] 1.646: [Unloading, 0.0011301 secs], 0.0074056 secs]
[Times: user=0.01 sys=0.00, real=0.01 secs]
Phase 5:Cleanup标沪。 并發(fā)標(biāo)記的最終階段榄攀,為即將到來的evacuation pause做準(zhǔn)備:計(jì)算堆區(qū)域塊所有活對象,根據(jù)期望的GC效益來排序這些區(qū)域塊金句。為了下次并發(fā)標(biāo)記檩赢,還需要做一些清理工作來維護(hù)內(nèi)部數(shù)據(jù)結(jié)構(gòu)的狀態(tài)。
最后還有一件重要的事是违寞,沒有包含活對象的區(qū)域塊會(huì)在這個(gè)階段被回收漠畜。這個(gè)階段有些部分是并發(fā),比如空區(qū)域塊回收坞靶,大部分的活性計(jì)算憔狞,但也需要一個(gè)短暫的STW停頓來結(jié)束整個(gè)過程,以防止應(yīng)用程序干預(yù)彰阴。這個(gè)停頓的日志如下:
1.652: [GC cleanup 1213M->1213M(1885M), 0.0030492 secs]
[Times: user=0.01 sys=0.00, real=0.00 secs]
當(dāng)出現(xiàn)某些堆區(qū)域塊只包含垃圾的時(shí)候瘾敢,日志格式稍微有點(diǎn)不同:
1.872: [GC cleanup 1357M->173M(1996M), 0.0015664 secs]
[Times: user=0.01 sys=0.00, real=0.01 secs]
1.874: [GC concurrent-cleanup-start]
1.876: [GC concurrent-cleanup-end, 0.0014846 secs]
Evacuation Pause:混合模式
如果并發(fā)清除操作能夠釋放掉老生代的整個(gè)區(qū)域塊那是最好不過了,但是經(jīng)常不是這樣尿这。在并發(fā)標(biāo)記結(jié)束之后簇抵,G1會(huì)在混合模式下工作,不僅僅是從年輕代區(qū)域收集垃圾射众,也會(huì)把一些老生代區(qū)域捎帶放到收集集合中碟摆。
混合模式的evacuation pause一般并不會(huì)緊跟著并發(fā)標(biāo)記階段。是否要進(jìn)行evacuation pause叨橱,受到很多規(guī)則和直覺影響典蜕。比如,如果在并發(fā)標(biāo)記階段已經(jīng)釋放了大部分老生代區(qū)域罗洗,就沒必要立馬進(jìn)行evacuation pause了愉舔。
因此有可能在并發(fā)標(biāo)記結(jié)束和混合模式evacuation pause之間,有很多次全年輕代模式evacuation pause伙菜。
被加入到收集集合中的老生代區(qū)域塊個(gè)數(shù)轩缤,加入的順序也是基于很多規(guī)則的。包括應(yīng)用的軟實(shí)時(shí)性能目標(biāo),并發(fā)標(biāo)記過程中收集到的活性數(shù)據(jù)和gc效益數(shù)據(jù)火的,JVM參數(shù)配置等壶愤。混合模式的evacuation pause過程跟全年輕代模式的evacuation pause大部分一樣馏鹤,這里我們主要介紹一下remembered sets概念公你。
remembered sets使得對不同堆區(qū)域進(jìn)行獨(dú)立GC成為可能。比如假瞬,如果對區(qū)域A,B,C進(jìn)行GC陕靠,我們需要知道是否有從區(qū)域D,E來的對象引用,這對影響到對一個(gè)對象是否是活著的判斷脱茉。但是遍歷整個(gè)堆對象圖非常耗時(shí)剪芥,這也不符合增量回收的初衷,因此這里需要進(jìn)行優(yōu)化琴许。類似于其他垃圾收集算法使用卡片表來實(shí)現(xiàn)對年輕代的獨(dú)立回收税肪,在G1這里就是remembered sets。
如下圖所示榜田,每一個(gè)區(qū)域塊都有一個(gè)remembered set益兄,里面記錄了所有來自外部的引用,這些引用將被認(rèn)為是GC roots的補(bǔ)充箭券。注意在并發(fā)標(biāo)記過程中被判定為垃圾的老生代區(qū)域的對象會(huì)被無視净捅,即使有來自外部的引用指向這些對象,這個(gè)時(shí)候這些引用方也是垃圾辩块。
接下來就跟其他垃圾收集器一樣:多個(gè)并行GC線程找出來哪些對象是活的蛔六,哪些是垃圾。
最后废亭,活對象被拷貝到存活區(qū)国章,如果需要的話會(huì)創(chuàng)建一個(gè)新的存活區(qū)。這樣空區(qū)域被釋放了豆村,可以用來存儲新對象了液兽。
為了維護(hù)這個(gè)remembered set,在應(yīng)用程序運(yùn)行過程中掌动,當(dāng)需要對某個(gè)字段進(jìn)行寫操作就會(huì)觸發(fā)一個(gè)Post-Write Barrier四啰。如果最終的引用是跨區(qū)的,比如從一個(gè)區(qū)域指向另外一個(gè)區(qū)域坏匪,這個(gè)時(shí)候在目標(biāo)區(qū)域的remembered set里面就會(huì)相應(yīng)追加一條記錄拟逮。為了減少Write Barrier的開銷,把這個(gè)卡片放入到remembered set里面的操作是異步的适滓,并采取了一些其他的優(yōu)化措施×底罚基本來說分幾個(gè)步驟:Write Barrier把臟卡片信息放入到一個(gè)本地緩存凭迹,一個(gè)專屬GC線程讀取它再把這個(gè)信息告訴給被引用區(qū)域的remembered set罚屋。
混合模式的日志里面會(huì)輸出它自己獨(dú)有的一些信息。
[Update RS (ms)1: Min: 0.7, Avg: 0.8, Max: 0.9, Diff: 0.2, Sum: 6.1]
[Processed Buffers2: Min: 0, Avg: 2.2, Max: 5, Diff: 5, Sum: 18]
[Scan RS (ms)3: Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.8]
[Clear CT: 0.2 ms]4
[Redirty Cards: 0.1 ms]5
- [Update RS (ms):因?yàn)閷S的操作是并發(fā)的嗅绸,我們必須確保在實(shí)際收集開始之前要處理完緩存中的卡片脾猛。如果這個(gè)數(shù)字很高的話,說明并發(fā)GC線程無法處理這個(gè)負(fù)荷鱼鸠,這可能是因?yàn)榇罅康淖侄涡薷膶?dǎo)致也可能是因?yàn)镃PU資源不夠?qū)е?/li>
- [Processed Buffers:每個(gè)工作線程處理本地緩存次數(shù)
- [Scan RS (ms):掃描RS里面引用耗時(shí)
- [Clear CT: 0.2 ms]:清除卡片表中卡片耗時(shí)猛拴,就是簡單去除字段的臟數(shù)據(jù)標(biāo)簽
- [Redirty Cards: 0.1 ms]:在卡片表中的適當(dāng)位置標(biāo)記臟數(shù)據(jù)耗時(shí),這個(gè)位置是有GC對堆的修改決定的蚀狰,比如在對引用進(jìn)行入隊(duì)列時(shí)
總結(jié)
這節(jié)詳細(xì)全面介紹了G1是如何工作的愉昆。當(dāng)然還是有些實(shí)現(xiàn)細(xì)節(jié)是簡要帶過的,比如如何處理humongous objects麻蹋,整體來講跛溉,G1代表了HotSpot里面商用垃圾收集器的最前端技術(shù)成果。隨著Java的版本的不斷發(fā)布扮授,對G1的優(yōu)化也一直在進(jìn)行芳室。
從中可以看出,G1解決了CMS面臨的很多問題刹勃,從停頓的可預(yù)見性到堆內(nèi)存碎片堪侯。假如一個(gè)應(yīng)用不受限于CPU利用率,但是對每個(gè)操作的延遲非常敏感荔仁,對HotSpot用戶來說抖格,G1就是最佳選擇了,特別是當(dāng)運(yùn)行在最新版Java上的應(yīng)用咕晋。但是雹拄,降低延遲并不是沒有代價(jià)的:因?yàn)橛蓄~外的write barriers和更多活躍的后臺線程,G1的吞吐量開銷要更大一些掌呜。所以滓玖,如果應(yīng)用程序更看重吞吐量或者已經(jīng)耗盡了CPU資源,不太在意每次停頓時(shí)長质蕉,CMS或者并行算法的垃圾收集器會(huì)更加適合势篡。
要想選擇合適的GC算法和參數(shù)配置,唯一可行的方法就是不停的試錯(cuò)模暗。但是我們會(huì)在下章給出一些基本的指導(dǎo)規(guī)則禁悠。
*注意:G1可能成為Java 9的默認(rèn)GC:http://openjdk.java.net/jeps/248 *
Shenandoah
我們已經(jīng)介紹了HotSpot中所有可用的商用GC算法。還有一個(gè)正在開發(fā)中的兑宇,被稱作“Ultra-Low-Pause-Time”垃圾收集器碍侦。它是專門為大型多核,大內(nèi)存堆的服務(wù)器而設(shè)計(jì)的,其目標(biāo)是在堆內(nèi)存容量在100GB+的時(shí)候瓷产,停頓能控制在10ms以內(nèi)站玄。當(dāng)然這個(gè)同樣是以降低應(yīng)用程序吞吐量為代價(jià)的:設(shè)計(jì)者的目標(biāo)是用不超過10%的性能損失換取零GC停頓。
在該算法可用于生產(chǎn)環(huán)境之前濒旦,我們這里就不深入介紹具體的算法實(shí)現(xiàn)了株旷,但是它仍然是基于這里講的很多概念的,比如并發(fā)標(biāo)記尔邓,增量回收等晾剖。當(dāng)然它也有很多不同的實(shí)現(xiàn),它不對堆內(nèi)存進(jìn)行分代梯嗽,只有一個(gè)空間齿尽。對的,它不是分代垃圾回收器慷荔。這樣它就沒有card tables和remembered sets的概念雕什。它也使用forwarding pointers和Brooks style read barrier來實(shí)現(xiàn)并發(fā)拷貝活對象,從而降低停頓的次數(shù)和持續(xù)時(shí)間显晶。
更多關(guān)于Shenandoah算法進(jìn)展可以關(guān)注這個(gè)博客