原文地址http://blog.csdn.net/zhshulin/article/details/50614477
通過學(xué)習(xí)了解到現(xiàn)在商用的JVM中的垃圾收集采用的是分代收集算法,即針對不同年代采用不同的收集算法。在JVM中,GC主要作用于堆內(nèi)存中,堆內(nèi)存又被劃分為新生代和老年代,由于新生代對象絕大多數(shù)是朝生夕死,而老年代相對存活時(shí)間就很長,故而需要使用不同的垃圾收集機(jī)制,所以垃圾收集器也就分為新生代收集器和老年代收集器,兩者相互組合進(jìn)行JVM堆內(nèi)存的空間回收(下圖中相連的垃圾收集器表示可以相互組合,注意Serial Old和CMS也可以聯(lián)合進(jìn)行老年代的垃圾收集)。JDK6u14中開始測試的G1垃圾收集器,正式發(fā)布于JDK7u4中,是目前唯一不需要依賴其他垃圾收集器即可完成新生代和老年代內(nèi)存收集。閱讀之前先了解,GC的兩個(gè)指標(biāo):暫停時(shí)間-應(yīng)對與存在大量用戶交互的場景;吞吐量-應(yīng)對后臺計(jì)算任務(wù)。
新生代的垃圾收集器有:Serial收集器、ParNew收集器、Parallel Scavenge收集器老年代的垃圾收集器有:Serial Old收集器、Parallel Old收集器、CMS收集器G1收集器。http://f.dataguru.cn/thread-514678-1-1.html
筆者使用的是JDK7u51,也就是JDK1.7.0_51
下面我將試著通過自己的理解來分析各個(gè)垃圾收集器的特點(diǎn),目前并沒有一個(gè)適用于任何場景的垃圾收集器,所以選擇何種垃圾收集器進(jìn)行配合是根據(jù)具體應(yīng)用來區(qū)別對待的,那么了解各種垃圾收集器的特點(diǎn)以及他們之間是否可以相互配合,就十分重要了。
垃圾收集器運(yùn)行過程中必然會(huì)發(fā)生“Stop the world”,只是時(shí)間長短和暫停時(shí)間可不可控的區(qū)別。
在進(jìn)行下面的閱讀之前,首先明確在垃圾收集器中,“并發(fā)”和“并行”這兩個(gè)概念的差別:
并行(Parallel):多個(gè)垃圾收集線程并行工作,此時(shí)用戶線程處于等待狀態(tài)并發(fā)(Concurrent):垃圾收集線程和用戶線程同時(shí)執(zhí)行(不一定是并行,可能是交替執(zhí)行),用戶程序繼續(xù)執(zhí)行,而GC運(yùn)行在另一個(gè)CPU上Serial垃圾收集器,通過這個(gè)單詞的意思“連續(xù)”,我認(rèn)為這個(gè)應(yīng)該是指的GC之后內(nèi)存空間不存在內(nèi)存碎片的意思,那么必然不會(huì)采用“標(biāo)記-清除算法”來實(shí)現(xiàn),所以這個(gè)垃圾收集器在新生代使用的是“復(fù)制算法”,而Serial Old作為Serail收集器的老年代版本,使用的就是“標(biāo)記-整理算法”。
為什么先說Serial垃圾收集器,是因?yàn)檫@個(gè)收集器是最基本、歷史最悠久的收集器,在JDK1.3.1之前,是JVM新生代收集的唯一選擇。Serial收集器是一個(gè)單線程的收集器,這個(gè)“單線程”是指JVM在使用它進(jìn)行GC的時(shí)候,必須暫停其他所有的工作線程(sun將這件事情稱為“Stop the world”),直到GC完成,這是一件非常可怕的事情。看到這里,你可能想我一定要修改我的JVM的新生代收集器,不用Serial了,但是直至現(xiàn)在,Serial依然是JVM在運(yùn)行Client模式下默認(rèn)的新生代 收集器。與其他垃圾收集器的單線程相比,Serial簡單而高效。對于用戶桌面應(yīng)用場景來說,分配給JVM的內(nèi)存一般不會(huì)太大,收集十幾甚至一兩百兆的內(nèi)存,停頓時(shí)間可以控制在幾十毫秒,最多一百多毫秒以內(nèi),只要不是特別頻繁,這些停頓還是可以接受的。所以,對于Client模式下的JVM來說,Serial是個(gè)很好的新生代收集器,簡單高效。

ParNew收集器也是一個(gè)新生代收集器,其實(shí)就是Serial收集器的多線程版本,是一個(gè)“并行”的垃圾收集器,除了多線程外,其他和Serial差不多。想想也就明白了,當(dāng)JVM團(tuán)隊(duì)開發(fā)出來了Serial,可以滿足Client模式下的JVM,但是對于Server模式下的JVM來說,運(yùn)行很長時(shí)間,有很多的對象需要收集(可能幾十個(gè)G),單線程導(dǎo)致的停頓時(shí)間太長了(比如每運(yùn)行1小時(shí)需要停頓5分鐘),用戶無法接受業(yè)務(wù)線程停頓那么長的時(shí)間,我猜測這種情況下那些大牛能想到的最簡單的辦法就是讓Serial變成多線程,這樣開多個(gè)線程就可以有效的降低停頓時(shí)間,故而這個(gè)Serial的多線程版本也就誕生了。
ParNew是許多運(yùn)行在Server模式下的JVM中首選的垃圾收集器,其中一個(gè)重要原因就是除了Serial,它是唯一可以和CMS(Concurrent Mark Sweep)老年代垃圾收集器配合工作。
ParNew在單CPU環(huán)境中收集效果不如Serial收集器,但是隨著CPU的增加,它對于GC時(shí)系統(tǒng)資源的利用還是很有好處的,默認(rèn)開啟的線程數(shù)與CPU的數(shù)量一致 。

Parallel Scavenge收集器,簡稱PS收集器,它和ParNew收集器一樣是一個(gè)多線程的并行新生代垃圾收集器,一樣采用“復(fù)制算法”(始發(fā)于JDK1.4.0)。那為什么還要這個(gè)PS收集器呢?現(xiàn)在想一下,ParNew收集器為什么會(huì)產(chǎn)生,不就是閑Serial收集器導(dǎo)致的“Stop the world”的時(shí)間太長了嘛,搞個(gè)多線程,減少停頓時(shí)間。這種的垃圾收集器適合重視服務(wù)的響應(yīng)速度的應(yīng)用程序(比如購物網(wǎng)站,肯定希望停頓時(shí)間越短越好,這樣用戶體驗(yàn)才好),但是對于一個(gè)后臺計(jì)算任務(wù)(比如MaPReduce)來說,沒有太多的交互任務(wù),那么它所重視的就不是這種響應(yīng)速度,而是CPU的有效時(shí)間利用率(這是我的理解),官方稱之為“吞吐量(Throughtput)”。吞吐量就是指CPU用來運(yùn)行用戶代碼的時(shí)間和CPU的總消耗時(shí)間的比值,即吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+GC消耗的時(shí)間)。
Parallel Scavenge收集器正是基于對“吞吐量”的追求而產(chǎn)生的,它的目標(biāo)就是達(dá)到一個(gè)可控的吞吐量。由于與吞吐量關(guān)系密切,Parallel Scavenge收集器也被稱為“吞吐量優(yōu)先“收集器。Parallel Scavenge提供了兩個(gè)參數(shù)用來精確控制吞吐量,分別是控制最大垃圾收集停頓時(shí)間的-XX:MaxGCPauseMillis參數(shù)(單位:毫秒),以及直接設(shè)置吞吐量大小的-XX:GCTimeRatio參數(shù)(0-100之間,不包括首尾)。GCTimeRatio參數(shù)的計(jì)算規(guī)則是,比如設(shè)成19,那么允許最大時(shí)間就占總時(shí)間的5%,即1/(1+19),默認(rèn)值是99,也就是默認(rèn)允許最大GC時(shí)間占比是1%。
Parallel Scavenge收集器還有一個(gè)參數(shù)來開啟GC的自適應(yīng)調(diào)節(jié)策略,只需要將JVM基本內(nèi)存設(shè)置好,并且制定上述兩個(gè)參數(shù)中的一個(gè)來作為JVM的優(yōu)化目標(biāo),那么JVM就可以根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息,動(dòng)態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時(shí)間或者最大吞吐量,這個(gè)參數(shù)就是-XX:+UseAdaptiveSizePolicy。自適應(yīng)調(diào)節(jié)策略也是PS收集器 相對于ParNew收集器的一個(gè)重要區(qū)別。ParNew收集器需要手工指定新生代大小(-Xmn)、Eden與Survivor的比例(-XX:SurvivorRatio)、晉升老年代對象年齡(-XX:PretenureSizeThreshold)等細(xì)節(jié)參數(shù)。



Parallel Old是Parallel Scavenge收集器的老年代版本,簡稱PS Old,使用了多線程和”標(biāo)記-整理“算法,這個(gè)收集器是在JDK1.6中才提供的,在此之前PS new的地位比較尷尬,因?yàn)樵诖酥袄夏甏睦厥罩挥蠸erial Old這一種收集器,與Serial Old配合,Parallel Scavenge無法產(chǎn)生理想的回收效果,吞吐量在老年代很大且硬件比較高級的環(huán)境中可能還不如使用ParNew與CMS的組合”給力“,而PS Old產(chǎn)生之后,PS New才變得名副其實(shí)。 也就是說,在JDK1.6及之后,在注重吞吐量和CPU資源敏感的場合,都可以優(yōu)先考慮PS New和PS Old的組合。

在JDK6u14中提供了Early access版本的G1收集器以供試用,直到JDK7u4的時(shí)候才正式發(fā)布。G1是一款面向服務(wù)端應(yīng)用的垃圾收集器,HotSpot開發(fā)團(tuán)隊(duì)賦予它的使命是未來替換掉CMS收集器,從這點(diǎn)上看,G1也是追求短停頓時(shí)間的。
G1收集器與上述的6種收集器相比,具有以下的特點(diǎn):
并行和并發(fā):G1收集器不僅能充分利用多CPU、多核環(huán)境下的硬件優(yōu)勢來減少停頓時(shí)間,而且仍可以通過并發(fā)的方式讓用戶線程繼續(xù)執(zhí)行分代收集:雖然在使用G1收集器的時(shí)候,JAVA堆的內(nèi)存布局已經(jīng)不再是物理隔離了,僅僅是邏輯隔離,但是分代的概念得到保留,G1可以獨(dú)立管理整個(gè)GC堆空間整合:CMS采用了“標(biāo)記-清除”算法,會(huì)產(chǎn)生內(nèi)存碎片。而G1整體看來采用的是“標(biāo)記-整理”算法,局部看來采用的是“復(fù)制”算法,故而G1運(yùn)行期間都不會(huì)產(chǎn)生內(nèi)存碎片,這種特性有利于程序長時(shí)間的運(yùn)行。可預(yù)測停頓:這是G1相比較CMS的一大優(yōu)勢,G1除了和CMS一樣追求低停頓外,還能建立可預(yù)測的停頓模型,能讓使用者明確指定在一個(gè)長度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾收集上的時(shí)間不超過N毫秒,這幾乎已經(jīng)是實(shí)時(shí)JAVA(RTSJ)垃圾收集器的特征了。G1最大的特點(diǎn)在我看來就是G1將Java堆劃分成多個(gè)大小相等的獨(dú)立區(qū)域(Region),雖然保留了新生代和老年代的概念,但是它們已經(jīng)不再是物理隔離了,而都是一部分Region的集合。G1在后臺維護(hù)一個(gè) 優(yōu)先列表,這個(gè)列表中保存了G1收集到的各個(gè)Region里面的垃圾堆積的價(jià)值大小(回收所獲得的空間大小以及回收需要的時(shí)間的經(jīng)驗(yàn)值),當(dāng)需要進(jìn)行垃圾回收的時(shí)候,根據(jù)用戶允許的收集時(shí)間,優(yōu)先回收列表中價(jià)值最大的那個(gè)Region(這就是Garbage-First,G1名字的由來)。這種使用Region劃分空間以及根據(jù)優(yōu)先級的區(qū)域回收方式,保證了G1收集器可以在有效的時(shí)間內(nèi)獲得盡可能高的收集效率,同時(shí)也避免了在整個(gè)JAVA堆中進(jìn)行全區(qū)域的垃圾收集。
G1產(chǎn)生的原因我認(rèn)為就是HotSpot團(tuán)隊(duì)對于低延時(shí)和吞吐量兩者同時(shí)考慮,不斷追求一個(gè)完美的垃圾收集器的產(chǎn)物,雖然在執(zhí)行流程上和CMS有差不多,并且在初始標(biāo)記和最終標(biāo)記階段都需要暫停用戶線程,但是通過重新定義JAVA堆,引出了Region的概念,成功的讓其在性能上能兼顧到低延時(shí)和吞吐量,且不需要依賴其他收集器。G1的未來就是優(yōu)化初始標(biāo)記和最終標(biāo)記階段,如果能解決這兩個(gè)階段的用戶線程暫停,實(shí)現(xiàn)并發(fā),那么就很有可能產(chǎn)生一個(gè)近似理想狀態(tài)的一個(gè)垃圾收集器。
不過用戶對于新生事物必須的認(rèn)同需要一定的時(shí)間,并且之前垃圾收集器相互配合也可以滿足用戶需求,那么G1對于大多數(shù)公司來說不是必需品,但是,隨著技術(shù)的不斷成熟,我認(rèn)為G1很有可能成為Server模式下的HotSpot默認(rèn)的收集器。
對于G1收集器,可以參考:http://f.dataguru.cn/thread-514678-1-1.html

新聞熱點(diǎn)
疑難解答