JVM 探究(三):垃圾回收算法和垃圾回收器

通過介紹當(dāng)前的垃圾回收器和垃圾回收算法的對比和不同的優(yōu)勢咙轩,來幫助讀者選擇適合自己的垃圾回收器。主要涉及對象存活的判斷、三種垃圾回收算法以及新生代和老年代的幾種傳統(tǒng)的收集器斥铺,最后會介紹一下比較火熱的Garbage First(G1)收集器

JVM 內(nèi)存分配方式

在 Java 虛擬機需要一種方式來分配對象急但,就像操作系統(tǒng)需要管理內(nèi)存一樣澎媒,Java 虛擬機也需要一種方式來管理 Java 虛擬機的內(nèi)存,現(xiàn)在管理 Java 虛擬機的方式主要有兩種波桩,一種被稱為指針碰撞(Bump the Point)戒努,另外一種被稱為空閑列表(Free List).

指針碰撞

這種內(nèi)存分配方式需要配合擁有內(nèi)存整理過程的垃圾收集器。簡單來說镐躲,這種方式的內(nèi)存管理方式就是在空閑內(nèi)存和已用內(nèi)存之間設(shè)置一個指針储玫,如果有新的內(nèi)存被分配,就需要將指針向空閑區(qū)域移動對應(yīng)的大小匀油。然而由于這種方式簡單粗暴缘缚,就必須要對應(yīng)的垃圾收集器能夠?qū)⒋婊畹膶ο笳加玫膬?nèi)存整理在指針的一側(cè)。使用這種分配算法的垃圾收集器是Serial,ParNew等帶有內(nèi)存整理的垃圾收集器敌蚜。

空閑列表

通過在虛擬機中維護(hù)一個列表來記錄那些內(nèi)存空間是能夠使用的桥滨。如果需要分配新的對象,將會在空閑的列表中尋找一個足夠大的空閑內(nèi)存來分配對象弛车,然后更新這個空閑列表齐媒。采用標(biāo)記-清除算法的垃圾收集器(eg:CMS)會采用這種分配方式。但是這種分配方式很容易造成很多外部碎片纷跛,在一定程度上會造成內(nèi)存的浪費喻括。

如何定位需要訪問的對象

如何通過變量表引用到需要用到的對象,通過引用我們要找到具體的對象贫奠,然后才能夠?qū)ο笞鲆恍┝械牟僮骰QR梅绞街饕幸幌聝煞N方式

句柄方式

句柄方式訪問對象

句柄方式通過在 Java 堆中維護(hù)一個句柄池,池中的維護(hù)了對象實例和對象類型的引用唤崭,然后在根據(jù)這兩個信息分別找到對應(yīng)的存儲信息拷恨,但是這樣做的一個確定是實例對象的類型和數(shù)據(jù)都是通過兩次尋址的方式來找到的。

直接引用

直接引用方式訪問對象

直接引用的方式相較于句柄的方式谢肾,在第一次尋址的時候就能夠找到對象的實例數(shù)據(jù)腕侄,對象類型數(shù)據(jù)的指針和對象示例放到了一起,從而一次尋址就能夠找到對象的示例數(shù)據(jù)芦疏。

垃圾回收算法

介紹常用的垃圾回收算法冕杠,針對HotSpot虛擬機做一些深入的介紹。

Mark-Sweep算法

標(biāo)記-清除(Mark-Sweep)算法回收前后內(nèi)存占用情況如下:

標(biāo)記-清除算法圖示

標(biāo)記-清除(Mark-Sweep)算法是通過可以回收的內(nèi)存進(jìn)行標(biāo)記后做清除操作酸茴,這樣做的缺點就是會產(chǎn)生很多的內(nèi)存碎片分预,可能的結(jié)果就是空閑內(nèi)存總量是能夠進(jìn)行新對象的分配的,但是由于這些空閑的空間都不連續(xù)薪捍,一個對象放不下噪舀。就必須進(jìn)行另外一次垃圾回收魁淳,垃圾回收又回引起服務(wù)的暫時停頓。

Mark-Compact 算法

標(biāo)記-整理(Mark-Compact)算法回收前后內(nèi)存占用情況如下:

標(biāo)記-整理算法圖示

標(biāo)記-整理(Mark-Compact)算法解決了標(biāo)記-清除(Mark-Sweep)算法會產(chǎn)生很多不連續(xù)的空閑空間做出了改進(jìn)与倡,具體的方法就是在進(jìn)行標(biāo)記之后不是將對象直接清理掉界逛,而是將存活的對象進(jìn)行整理,使得存活的對象占用一塊連續(xù)的內(nèi)存空間纺座,因為存活對象和可回收空間有明顯的分割息拜,所以可以直接對邊界之外的內(nèi)存進(jìn)行直接的清理。這樣的清理無疑更加有效率净响,雖然增加了移動對象的開支少欺,但是整體上會比標(biāo)記-清除(Mark-Sweep)算法更有效率。

Copying 算法

復(fù)制(Copying)算法回收前后內(nèi)存占用情況如下:

復(fù)制算法圖示

復(fù)制(Copying)算法將內(nèi)存區(qū)域分為大小相同的兩個區(qū)域馋贤,當(dāng)需要內(nèi)存回收的時候赞别,只需要將存活的對象復(fù)制到另外一塊空閑區(qū)域,然后將原來的區(qū)域一次清理干凈配乓,什么都不留仿滔。相較于標(biāo)記-清除(Mark-Sweep)算法的優(yōu)勢就是在解決了在標(biāo)記和清除階段的低效率和內(nèi)存碎片問題,相較于標(biāo)記-整理(Mark-Compact)算法的優(yōu)勢則是有著固定的內(nèi)存分割線犹芹,而不是動態(tài)的調(diào)整崎页,同時由于有兩塊相同大小的內(nèi)存區(qū)域,存活的對象的復(fù)制和內(nèi)存的回收會更有效率腰埂。缺點也十分明顯飒焦,就是需要的內(nèi)存空間是可用空間的2倍,內(nèi)存的使用率永遠(yuǎn)不會超過50%屿笼。(現(xiàn)代虛擬機根據(jù)經(jīng)驗優(yōu)化了這個比例牺荠,在后面介紹收集器的時候會介紹)

聊聊HotSpot

HotSpot 是現(xiàn)在最廣泛使用的虛擬機,擁有出色的性能驴一。

判斷對象是否存活

目前針對對象的判斷生存狀態(tài)的的算法主要有引用計數(shù)法和可達(dá)性分析兩種休雌。在Object-C等語言中使用的是引用計數(shù)法,在HotSpot中蛔趴,是使用可達(dá)性分析來判斷的挑辆。

引用計數(shù)法(Reference Counting)

引用計數(shù)法是想要解決引用問題最容易想到的一種算法例朱,既然需要知道對象是否存活孝情,那就在對象上加個計數(shù)器來表示自己被引用的次數(shù)唄,每有一次新的引用洒嗤,這個計數(shù)器就加1箫荡,每少一次引用,計數(shù)器就減1渔隶。這樣看來羔挡,事情似乎出奇的簡單洁奈,因為當(dāng)該對象的計數(shù)器為0的時候,這就是這個對象的死期了绞灼。這種方式十分高效利术,而且簡單。

引用計數(shù)方法對象的的數(shù)據(jù)結(jié)構(gòu)

<center>引用計數(shù)方法對象的的數(shù)據(jù)結(jié)構(gòu)</center>

事情到此看起來都很美好低矮。然而印叁,卻有一種循環(huán)引用的問題讓想使用這種算法的 GC 退縮。那便是循環(huán)引用問題军掂,像下圖這種轮蜕,兩個對象相互引用,但是沒有其他對象引用這兩個對象的情況蝗锥,實際上這兩個對象已經(jīng)都沒有利用價值了跃洛,應(yīng)該讓 GC 回收≈找椋可是由于兩個對象互相都持有對方的一個引用汇竭,讓 GC 以為他們都還需要繼續(xù)生存以服務(wù)另外的對象。長期下去會造成很嚴(yán)重的內(nèi)存泄露痊剖。

循環(huán)引用對象

<center>循環(huán)引用對象</center>

除了上面這種最簡單的引用計數(shù)法韩玩,還有延遲引用計數(shù)法Sticky引用計數(shù)法。這里不再深入討論陆馁,可以參考 GC引用計數(shù)算法 找颓,或者直接閱讀《垃圾回收的算法與實現(xiàn)》

可達(dá)性分析(Reachablity Analysis)

引用計數(shù)法相比叮贩,可達(dá)性分析就顯得更加復(fù)雜了击狮。可達(dá)性分析是通過GC Root開始搜索整個對象池益老,如果某些對象無法搜索到彪蓬,在圖論中我們稱之為不可達(dá),那么這些不可達(dá)的對象就會成為GC的刀下鬼了捺萌。通過這種方式就能夠?qū)崿F(xiàn)回收循環(huán)引用的對象档冬。

[圖片上傳失敗...(image-10d21e-1516334222313)]
<center>可達(dá)性分析判斷對象是否可回收</center>

HotSpotGC Root主要有

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象。
  • 方法區(qū)中類靜態(tài)屬性引用的對象桃纯。
  • 方法區(qū)中常量引用的對象
  • 本地方法棧中JNI(即一般說的Native方法)引用的對象
HotSpot 的可達(dá)性分析實現(xiàn)

在使用可達(dá)性分析算法的時候酷誓,不可避免地會引起GC 停頓,因為必須保證在進(jìn)行可達(dá)性分析的時候?qū)ο笾g的引用關(guān)系是不變的态坦,我們無法針對一個在不斷變化的圖分析他的可達(dá)性盐数。因此在進(jìn)行可達(dá)性分析的時候難免要停止所有的用戶線程。我們通常稱之為STW(Stop The Word). 有些收集器通過一些手段可以減少STW的時間(例如CMS)伞梯,但是無法跳過這個過程玫氢。

目前主流的 Java 虛擬機使用的都是準(zhǔn)確式GC帚屉,就是說當(dāng)STW發(fā)生的時候,并不需要檢查全部的變量從而確定那些變量是引用類型漾峡,而是在類加載完成之后會將引用類型的變量存在一個數(shù)據(jù)結(jié)構(gòu)中攻旦,在HotSpot虛擬機中使用OopMap來記錄。

準(zhǔn)確式GC解決了引用類型的定位問題敬特,但是引用類型之間的關(guān)系錯綜復(fù)雜,如果為每一條指令都生成對應(yīng)OopMap牺陶,那么GC的空間成本就會很大伟阔。為了解決這問題,引入了安全點(Safepoint)安全區(qū)域(Safe Region)兩個概念掰伸。HotSpot只有在安全點(Safepoint)安全區(qū)域(Safe Region)這兩個位置才會生成相應(yīng)的OopMap皱炉,這兩個區(qū)域的選擇標(biāo)準(zhǔn)是能夠讓程序長時間執(zhí)行(也就是這段時間內(nèi)不會發(fā)生引用關(guān)系的改變)的指令。

安全點一般設(shè)置在指令復(fù)用的代碼附近狮鸭,因為這些代碼比較符合“長時間執(zhí)行”的條件合搅,這些代碼的典型代表就是方法調(diào)用、循環(huán)跳轉(zhuǎn)歧蕉、異常跳轉(zhuǎn)等灾部。如何讓程序在安全點上停下里等待GC也有兩種方式,分別是搶先式中斷(Preemptive Suspension)主動式中斷(Voluntary Suspension). 搶先式中斷不需要程序配合惯退,當(dāng) GC 發(fā)生時會中斷所有的線程赌髓,然后找出不在安全點上的線程讓其繼續(xù)執(zhí)行到最近的安全點;主動式中斷需要程序進(jìn)行配合催跪,設(shè)置一個標(biāo)識位锁蠕,當(dāng)GC開始后這個標(biāo)識位置位,所有的進(jìn)程到達(dá)安全點之后會主動的去查詢這個標(biāo)識位懊蒸,如果為真時就會自動中斷掛起自己荣倾。

安全區(qū)域是配合安全點的一種機制,因為有些指令程序是“不執(zhí)行”的骑丸,例如線程處在 Sleep 狀態(tài)或者 Blocked 狀態(tài)舌仍,如果在 GC 的時候一直等待進(jìn)入安全點,會使 GC 停頓變得十分長通危。 當(dāng)線程執(zhí)行到安全區(qū)域的開始位置的時候铸豁,會標(biāo)識自己已經(jīng)進(jìn)入安全區(qū)域了,而在離開的時候會主動檢查系統(tǒng)是否已經(jīng)完成了根結(jié)點的枚舉黄鳍,如果沒有就繼續(xù)等待推姻,如果完成當(dāng)前線程才可以繼續(xù)執(zhí)行后面的代碼平匈。

垃圾收集器簡介

按照使用場景介紹常用的垃圾收集器框沟,介紹他們的適用場景藏古。讓讀者能夠根據(jù)自己的應(yīng)用場景來選擇合適的收集器。

垃圾收集器組合

<center>垃圾收集器組合和分類</center>

上圖中展示了各種收集器是新生代收集器還是老年代收集器忍燥,如果上面兩種收集器時間有連線拧晕,則表示可以配合使用。具體的GC配置參數(shù)可以參照 3. 常用垃圾收集器參數(shù)

首先說明兩個名次的含義梅垄,以免在下面的閱讀中產(chǎn)生歧義:

  • 并行(Parallel):指多條垃圾收集線程并行工作厂捞,但此時用戶線程仍然處于等待狀態(tài)
  • 并發(fā)(Concurrent):用戶線程與垃圾收集線程同時執(zhí)行(并行執(zhí)行,或者交替執(zhí)行)

新生代收集器

本文介紹的新生代收集器都是使用的復(fù)制算法

Serial 收集器

串行垃圾收集器队丝,說明他是單線程工作的靡馁,他只會使用一個CPU,使用一個線程去完成垃圾回收工作机久,同時這也意味著當(dāng)Serial GC進(jìn)行垃圾回收的時候臭墨,用戶線程也不得不暫停(STW)。Serial 收集器+Serial Old 收集器垃圾回收的時間線如下圖所示:

Serial/Serial Old

這樣的用戶線程對于現(xiàn)在的服務(wù)來說基本是不可接受的膘盖,減少 GC 停頓也一直是很多垃圾收集器的努力目標(biāo)胧弛。看似十分“無能”的Serial GC只能在客戶端中發(fā)揮作用侠畔,因為一般客戶端應(yīng)用需要的內(nèi)存并不是很大结缚,因此停頓幾十毫秒就可以完成內(nèi)存回收,這樣也是可以接受了软棺,同時省去了并行 GC 切換線程的開銷红竭。

ParNew 收集器

ParNew 收集器基本上就是Serial 收集器的升級版本,在新生代中收集的時候采用了并行的方案喘落。其他部分與Serial 收集器基本一樣德崭。該收集器在單 CPU 的環(huán)境下并不會有優(yōu)于Serial的表現(xiàn),揖盘,反而會由于線程的頻繁切換而降低性能眉厨,而對于多核CPU就會有較好的表現(xiàn)了。因此比較適合服務(wù)端程序兽狭。ParNew 收集器+Serial Old 收集器的 GC 過程如下圖所示:
[圖片上傳失敗...(image-eacfc3-1516334222313)]
雖然ParNew 收集器只是針對Serial 收集器的簡單升級憾股,但是有著十分廣泛的使用,原因就是能和CMS收集器搭配使用的只有Serial 收集器ParNew 收集器兩種箕慧。CMS的廣泛使用就帶來了ParNew的廣泛使用服球。

Parallel Scavenge 收集器

Parallel Scavenge 收集器也是一個新生代收集器,他也是并行的颠焦、使用復(fù)制算法的斩熊。和ParNew收集器非常相似,GC過程圖也與ParNew收集器相同伐庭。然而該收集器的關(guān)注點與其他收集器不同粉渠,其他收集器關(guān)注點都是如何縮短GC停頓時間分冈,而Parallel Scavenge 收集器關(guān)注的確實如何保持系統(tǒng)一個較高的吞吐量(吞吐量 = 運行用戶代碼的時間/(運行用戶代碼時間+垃圾收集時間))。因此該收集器主要適合那些交互較少霸株,運算較多的服務(wù)雕沉。該收集器允許用戶設(shè)置極少的參數(shù)就能保證一個較高的吞吐量。

該收集器提供兩個參數(shù)用于精確控制吞吐量-XX:MaxGCPauseMillis-XX:GCTimeRatio兩個參數(shù)去件,含義分別是最大GC停頓時間和直接設(shè)置吞吐量坡椒。GC的處理優(yōu)先級是MaxGCPauseMillis最高,GCTimeRatio次之尤溜,其他的空間大小配置優(yōu)先級最低倔叼。除了上面的兩個參數(shù)之外,還有一個參數(shù)-XX:+UseAdaptiveSizePolicy宫莱,當(dāng)設(shè)置了這個參數(shù)之后缀雳,就不需要再配置新生代的大小、Eden 和 Survivor 區(qū)域的比例梢睛、晉升老年代對象年齡等參數(shù)(設(shè)置了也沒用)肥印,虛擬機會根據(jù)當(dāng)前系統(tǒng)運行狀況來動態(tài)調(diào)整上面的幾個參數(shù)。

Parallel Scavenge(-XX:+UseParallelGC)框架下绝葡,默認(rèn)是在要觸發(fā)full GC前先執(zhí)行一次young GC深碱,并且兩次GC之間能讓應(yīng)用程序稍微運行一小下,以期降低full GC的暫停時間(因為young GC會盡量清理了young gen的死對象藏畅,減少了full GC的工作量)敷硅。控制這個行為的VM參數(shù)是-XX:+ScavengeBeforeFullGC

老年代收集器

Serial Old 收集器

Serial Old 收集器就是Serial 收集器的老年代版本愉阎,主要也是給Client模式下的虛擬機使用绞蹦。在 Server 應(yīng)用中用于和 PS 收集器搭配使用榜旦,還有就是在 CMS 失敗后,作為后備方案來進(jìn)行垃圾收集溅呢。

Serial/Serial Old

Parallel Old 收集器

Parallel Old 收集器是在JDK1.6中推出的,在此之前Parallel Scavenge 收集器只能和Serial Old 收集器搭配使用咐旧,由于Serial Old 收集器的拖累驶鹉,導(dǎo)致高吞吐量的優(yōu)勢無法體現(xiàn)出來铣墨,這種組合的吞度量甚至不如ParNew 收集器+CMS收集器的組合。而Parallel Old 收集器的正式推出表示在需要關(guān)注高吞吐量的服務(wù)中可以使用Parallel Scavenge 收集器+Parallel Old 收集器的組合。這種組合的 GC 回收過程線程運行情況如下圖:
[圖片上傳失敗...(image-62716-1516334222313)]

Concurrent Mark Sweep 收集器

CMS 收集器收集器是以最短的停頓時間為目標(biāo)的收集器姚淆,這樣的目標(biāo)可以保證服務(wù)的高可用性孕蝉,因此也成為了各種以交互為目的的服務(wù)的首選收集器肉盹。該收集器采用的是標(biāo)記-清除算法疹尾,而前面介紹的收集器都是采用的標(biāo)記-整理算法。該收集器 GC 過程相對復(fù)雜一些窍蓝。分為以下四個步驟:

  • 初始標(biāo)記(CMS initial mark)
  • 并發(fā)標(biāo)記(CMS concurrent mark)
  • 重新標(biāo)記(CMS final remark)
  • 并發(fā)清除(CMS concurrent sweep)
  • 重置線程(CMS concurrent reset)

這里截取一段 GC 日志

2017-11-07T16:20:41.582+0800: 2.119: [GC (CMS Initial Mark) [1 CMS-initial-mark: 0K(3670016K)] 26854K(4141888K), 0.0036737 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
2017-11-07T16:20:41.585+0800: 2.123: [CMS-concurrent-mark-start]
2017-11-07T16:20:41.587+0800: 2.125: [CMS-concurrent-mark: 0.002/0.002 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
2017-11-07T16:20:41.587+0800: 2.125: [CMS-concurrent-preclean-start]
2017-11-07T16:20:41.595+0800: 2.133: [CMS-concurrent-preclean: 0.008/0.008 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
2017-11-07T16:20:41.595+0800: 2.133: [CMS-concurrent-abortable-preclean-start]
2017-11-07T16:20:43.996+0800: 4.534: [CMS-concurrent-abortable-preclean: 1.514/2.401 secs] [Times: user=5.75 sys=0.28, real=2.40 secs]
2017-11-07T16:20:43.996+0800: 4.534: [GC (CMS Final Remark) [YG occupancy: 241272 K (471872 K)]4.534: [Rescan (parallel) , 0.0123556 secs]4.546: [weak refs processing, 0.0000203 secs]4.546: [class unloading, 0.0059852 secs]4.552: [scrub symbol table, 0.0055144 secs]4.558: [scrub string table, 0.0006434 secs][1 CMS-remark: 0K(3670016K)] 241272K(4141888K), 0.0260691 secs] [Times: user=0.11 sys=0.00, real=0.02 secs]
2017-11-07T16:20:44.023+0800: 4.560: [CMS-concurrent-sweep-start]
2017-11-07T16:20:44.023+0800: 4.560: [CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
2017-11-07T16:20:44.023+0800: 4.560: [CMS-concurrent-reset-start]
2017-11-07T16:20:44.041+0800: 4.578: [CMS-concurrent-reset: 0.018/0.018 secs] [Times: user=0.07 sys=0.02, real=0.02 secs]

real是程序的實際運行時間吓笙,sys是內(nèi)核態(tài)的時間巾腕,user是用戶態(tài)的時間,單核情況叁鉴,real遠(yuǎn)遠(yuǎn)大于user和sys之和佛寿。real,從程序開始到程序執(zhí)行結(jié)束時所消耗的時間常侣,包括CPU的用時和所有延遲程序執(zhí)行的因素的總和弹渔。CPU用時被劃分為user和sys兩塊。user表示程序本身巾乳,以及它所調(diào)用的庫中的子例程使用的時間鸟召。sys是由程序直接或間接調(diào)用的系統(tǒng)調(diào)用執(zhí)行的時間。real=cpu用時+其他因素時間,cpu 用時=user+sys,所以: real> user + sys (單核情況)

GC 過程中各個階段 GC 線程和用戶線程的運行關(guān)系如下圖:
[圖片上傳失敗...(image-d17a24-1516334222313)]

初始標(biāo)記压状、重新標(biāo)記這兩個步驟需要 STW, 初始標(biāo)記僅僅是標(biāo)記以下 GC Roots 能夠直接關(guān)聯(lián)到的對象,速度很快镣丑,并發(fā)標(biāo)記階段就是進(jìn)行GC RootsTracing的過程航夺。重新標(biāo)記是為了修正并發(fā)標(biāo)記期間由于用戶線程還在繼續(xù)執(zhí)行而產(chǎn)生變動的那一部分對象十兢,這個階段相比初始標(biāo)記要長一點,但是遠(yuǎn)比并發(fā)標(biāo)記短旱物。整體來看用時最多的幾個階段:并發(fā)標(biāo)記宵呛、并發(fā)清理、重置線程都會都是并發(fā)的宝穗,這樣可以讓應(yīng)用程序盡可能的減少停頓逮矛。

【關(guān)于CMS-concurrent-abortable-preclean】:從日志中我們還發(fā)現(xiàn)了一個細(xì)節(jié)叫做CMS-concurrent-abortable-preclean,這就要從Concurrent precleaning階段說起了橱鹏。Concurrent precleaning階段的實際行為是:針對新生代做抽樣莉兰,等待新生代在某個時間段(默認(rèn)5秒,可以通過CMSMaxAbortablePrecleanTime參數(shù)設(shè)置)執(zhí)行一次Minor GC杉辙,如果這個時間段內(nèi)GC沒有發(fā)生捶朵,那么就繼續(xù)進(jìn)行下一階段(Remark);如果時間段內(nèi)觸發(fā)了Minor GC品腹,則可能會執(zhí)行一些優(yōu)化(具體可以參考https://blogs.oracle.com/jonthecollector/entry/did_you_know

CMS 收集器也有明顯的幾個缺點:一是 CMS 默認(rèn)啟動的回收線程數(shù)是(CPU數(shù)量+3)/4红碑,也就是當(dāng) CPU 在 4 個以上時泡垃,并發(fā)垃圾回收時 GC線程占用不少于 25% 的 CPU 資源羡鸥,當(dāng) CPU 不足 4 個時惧浴,情況就變得更加嚴(yán)峻了。第二個缺點就是 CMS 無法處理浮動垃圾(Floating Garbage)衷旅,浮動垃圾是指在并發(fā)清理階段新產(chǎn)生的垃圾芜茵,由于 CMS 垃圾回收線程要和用戶線程并發(fā)倡蝙,因此必須要保留一部分內(nèi)存在回收期間供用戶線程使用,增額比例通過參數(shù)CMSInitiatingOccupancyFraction設(shè)置猪钮,表示老年代空間占用比例達(dá)到多少的時候會出發(fā)CMS GC胆建,這個值在JDK1.6中默認(rèn)值是68,在JDK1.7JDK1.8中都是92扑馁。如果在 CMS GC 期間剩余的老年代內(nèi)存空間不足與支持程序繼續(xù)執(zhí)行凉驻,就是觸發(fā)GC降級,也就是Concurrent Mode Failure,這個時候就需要使用Serial Old收集器來完成老年代回收的任務(wù)雄家,效率可想而知胀滚。最后一個缺點就是標(biāo)記-清除算法帶來的內(nèi)存碎片問題,這個問題可以通過參數(shù)-XX:+UseCMSCompactAtFullCollection來緩解(默認(rèn)開啟)顷编,用于在CMS進(jìn)行Full GC的時候進(jìn)行碎片的合并整理剑刑,但是內(nèi)存整理并不是并發(fā)的,會造成的應(yīng)用程序的停頓层宫,所以通常配合-XX:CMSFullGCsBeforeCompaction參數(shù)一起使用,該參數(shù)表示在允許連續(xù)幾次的不整理碎片的CMS GC限匣。

G1收集器

Garbage First 收集器應(yīng)該是當(dāng)今最前沿的垃圾收集器了毁菱,它的優(yōu)點是:

并行與并發(fā):G1能充分利用多CPU、多核環(huán)境下的硬件優(yōu)勢峦筒,使用多個CPU(CPU或者CPU核心)來縮短Stop-The-World停頓的時間窗慎,部分其他收集器原本需要停頓Java線程執(zhí)行的GC動作遮斥,G1收集器仍然可以通過并發(fā)的方式讓Java程序繼續(xù)執(zhí)行。

分代收集:與其他收集器一樣术吗,分代概念在G1中依然得以保留较屿。雖然G1可以不需要其他收集器配合就能獨立管理整個GC堆,但它能夠采用不同的方式去處理新創(chuàng)建的對象和已經(jīng)存活了一段時間购啄、熬過多次GC的舊對象以獲取更好的收集效果末贾。

空間整合:與CMS的“標(biāo)記—清理”算法不同,G1從整體來看是基于“標(biāo)記—整理”算法實現(xiàn)的收集器辉川,從局部(兩個Region之間)上來看是基于“復(fù)制”算法實現(xiàn)的拴测,但無論如何,這兩種算法都意味著G1運作期間不會產(chǎn)生內(nèi)存空間碎片屿愚,收集后能提供規(guī)整的可用內(nèi)存。這種特性有利于程序長時間運行,分配大對象時不會因為無法找到連續(xù)內(nèi)存空間而提前觸發(fā)下一次GC穷遂。

可預(yù)測的停頓:這是G1相對于CMS的另一大優(yōu)勢娱据,降低停頓時間是G1和CMS共同的關(guān)注點中剩,但G1除了追求低停頓外,還能建立可預(yù)測的停頓時間模型掠剑,能讓使用者明確指定在一個長度為M毫秒的時間片段內(nèi)郊愧,消耗在垃圾收集上的時間不得超過N毫秒,這幾乎已經(jīng)是實時Java(RTSJ)的垃圾收集器的特征了动分。

摘錄來自: 周志明. “深入理解Java虛擬機:JVM高級特性與最佳實踐(第2版)”红选。 iBooks.

G1 收集器雖然還有老年代和新生代的感念姆另,但是內(nèi)存布局上卻沒有為其劃定單獨的物理隔離的區(qū)域迹辐,而是分給它們不同數(shù)量(無需連續(xù))的Region。G1 能夠預(yù)測停頓的功能就是依賴于 Region 這個東西實現(xiàn)的间学,因為將內(nèi)存分割成多個大小相等的區(qū)域印荔,然后在后臺維護(hù)一個垃圾回收價值列表,每次根據(jù)允許的回收時間(使用參數(shù)-XX:MaxGCPauseMillis設(shè)定嘿悬,默認(rèn)值為200)來確定那些 Region 進(jìn)行回收水泉。理解起來很簡單的 Region 回收,實現(xiàn)起來卻很困難钢拧,其中一個主要的原因就是 Region 之間的對象相互引用源内,如果到回收時才進(jìn)行可達(dá)性分析要掃描整個Java堆才能完成分析,同樣的問題也存在于其他分代收集器新生代和老年代相互引用的關(guān)系中塔鳍。虛擬機使用Remembered Set來避免掃描全堆呻此,程序在對 Reference 類型進(jìn)行寫操作的時候會檢測引用的對象是否存在于不同的 Region(或者是不同年代)中,如果是就將應(yīng)用信息記錄到 被引用對象 的 Remembered Set 中掌唾,在內(nèi)存回收時忿磅,將 Remembered Set 加入 GC Roots 即可避免全堆掃描葱她。

G1 收集器的運作大致可以分為以下幾個步驟:

  1. 初始標(biāo)記(Initial Marking):僅僅標(biāo)記 GC Roots直接關(guān)聯(lián)的對象
  2. 并發(fā)標(biāo)記(Concurrent Marking):從 GC Roots開始做可達(dá)性分析
  3. 最終標(biāo)記(Final Marking):修正在并發(fā)標(biāo)記階段變動的標(biāo)記
  4. 篩選回收 (Live Data Counting and Evacuation):根據(jù)回收機制和成本排序,根據(jù)用戶期望的停頓時間制定回收計劃

線程運行情況如下圖:
[圖片上傳失敗...(image-b1baff-1516334222313)]

更多可參考Oracle關(guān)于G1調(diào)優(yōu)的官方文檔:Garbage First Garbage Collector Tuning

參考內(nèi)容

  1. GC引用計數(shù)算法
  2. 《垃圾回收的算法與實現(xiàn)》
  3. 《深入理解Java虛擬機:JVM高級特性與最佳實踐(第2版)》
  4. 部分圖片來源:深入理解JVM(3)——7種垃圾收集器
  5. Concurrent Mark Sweep (CMS) Collector
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市豪墅,隨后出現(xiàn)的幾起案子偶器,更是在濱河造成了極大的恐慌,老刑警劉巖颊郎,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亭枷,死亡現(xiàn)場離奇詭異叨粘,居然都是意外死亡瘤睹,警方通過查閱死者的電腦和手機答倡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門瘪撇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人恕曲,你說我怎么就攤上這事渤涌。” “怎么了茸俭?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵调鬓,是天一觀的道長酌伊。 經(jīng)常有香客問我,道長燕锥,這世上最難降的妖魔是什么悯蝉? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任鼻由,我火速辦了婚禮厚棵,結(jié)果婚禮上婆硬,老公的妹妹穿的比我還像新娘。我一直安慰自己彬犯,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布逻卖。 她就那樣靜靜地躺著昭抒,像睡著了一般灭返。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上熙含,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天婆芦,我揣著相機與錄音,去河邊找鬼肠鲫。 笑死或粮,一個胖子當(dāng)著我的面吹牛氯材,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播袋毙,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼冗尤,長吁一口氣:“原來是場噩夢啊……” “哼裂七!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起腰吟,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤徙瓶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后禾乘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體始藕,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年江耀,在試婚紗的時候發(fā)現(xiàn)自己被綠了祥国。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晾腔。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡灼擂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出睡腿,到底是詐尸還是另有隱情峻贮,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布挂捻,位于F島的核電站嚼黔,受9級特大地震影響唬涧,放射性物質(zhì)發(fā)生泄漏盛撑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一狮荔、第九天 我趴在偏房一處隱蔽的房頂上張望殖氏。 院中可真熱鬧,春花似錦雅采、人聲如沸婚瓜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胡陪。三九已至,卻和暖如春营曼,著一層夾襖步出監(jiān)牢的瞬間愚隧,已是汗流浹背狂塘。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留妈踊,地道東北人泪漂。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓萝勤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親慎式。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容