通過介紹當(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ū)ο笞鲆恍┝械牟僮骰QR梅绞街饕幸幌聝煞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)記-清除(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)記-整理(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ù)制(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的時候,這就是這個對象的死期了绞灼。這種方式十分高效利术,而且簡單。
<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)存泄露痊剖。
<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>
在HotSpot
中GC 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 收集器
垃圾回收的時間線如下圖所示:
這樣的用戶線程對于現(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)行垃圾收集溅呢。
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.7
和JDK1.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 收集器的運作大致可以分為以下幾個步驟:
- 初始標(biāo)記(Initial Marking):僅僅標(biāo)記 GC Roots直接關(guān)聯(lián)的對象
- 并發(fā)標(biāo)記(Concurrent Marking):從 GC Roots開始做可達(dá)性分析
- 最終標(biāo)記(Final Marking):修正在并發(fā)標(biāo)記階段變動的標(biāo)記
- 篩選回收 (Live Data Counting and Evacuation):根據(jù)回收機制和成本排序,根據(jù)用戶期望的停頓時間制定回收計劃
線程運行情況如下圖:
[圖片上傳失敗...(image-b1baff-1516334222313)]
更多可參考Oracle關(guān)于G1調(diào)優(yōu)的官方文檔:Garbage First Garbage Collector Tuning