原本想把題目更簡(jiǎn)單的定為--《不要停》的,但還是自己YY一下就算了。
Java開(kāi)發(fā)Server最大的障礙,就是JDK1.4版之前的的串行垃圾收集機(jī)制會(huì)引起長(zhǎng)時(shí)間的服務(wù)暫停,明白原理后,想想那些用JDK1.3寫Server的先輩,不得不后怕。
好在JDK1.4已開(kāi)始支持多線程并行的后臺(tái)垃圾收集算法,JDK5.0則優(yōu)化了默認(rèn)值的設(shè)置。
一、參考資料:
Tuning Garbage Collection with the 5.0 Java Virtual Machine 官方指南。
Hotspot memory management whitepaper 官方白皮書。
Java Tuning White Paper 官方文檔。
FAQ about Garbage Collection in the Hotspot 官方FAQ,JVM1.4.2。
Java HotSpot 虛擬機(jī)中的垃圾收集 JavaOne2004上的中文ppt
A Collection of JVM Options JVM選項(xiàng)的超完整收集。
二、基本概念
1、堆(Heap)
JVM管理的內(nèi)存叫堆。在32Bit操作系統(tǒng)上有1.5G-2G的限制,而64Bit的就沒(méi)有。
JVM初始分配的內(nèi)存由-Xms指定,默認(rèn)是物理內(nèi)存的1/64但小于1G。
JVM最大分配的內(nèi)存由-Xmx指定,默認(rèn)是物理內(nèi)存的1/4但小于1G。
默認(rèn)空余堆內(nèi)存小于40%時(shí),JVM就會(huì)增大堆直到-Xmx的最大限制,可以由-XX:MinHeapFreeRatio=指定。
默認(rèn)空余堆內(nèi)存大于70%時(shí),JVM會(huì)減少堆直到-Xms的最小限制,可以由-XX:MaxHeapFreeRatio=指定。
服務(wù)器一般設(shè)置-Xms、-Xmx相等以避免在每次GC 后調(diào)整堆的大小,所以上面的兩個(gè)參數(shù)沒(méi)啥用。
2.基本收集算法
復(fù)制:將堆內(nèi)分成兩個(gè)相同空間,從根(ThreadLocal的對(duì)象,靜態(tài)對(duì)象)開(kāi)始訪問(wèn)每一個(gè)關(guān)聯(lián)的活躍對(duì)象,將空間A的活躍對(duì)象全部復(fù)制到空間B,然后一次性回收整個(gè)空間A。
因?yàn)橹辉L問(wèn)活躍對(duì)象,將所有活動(dòng)對(duì)象復(fù)制走之后就清空整個(gè)空間,不用去訪問(wèn)死對(duì)象,所以遍歷空間的成本較小,但需要巨大的復(fù)制成本和較多的內(nèi)存。
標(biāo)記清除(mark-sweep):收集器先從根開(kāi)始訪問(wèn)所有活躍對(duì)象,標(biāo)記為活躍對(duì)象。然后再遍歷一次整個(gè)內(nèi)存區(qū)域,把所有沒(méi)有標(biāo)記活躍的對(duì)象進(jìn)行回收處理。該算法遍歷整個(gè)空間的成本較大暫停時(shí)間隨空間大小線性增大,而且整理后堆里的碎片很多。
標(biāo)記整理(mark-sweep-compact):綜合了上述兩者的做法和優(yōu)點(diǎn),先標(biāo)記活躍對(duì)象,然后將其合并成較大的內(nèi)存塊。
可見(jiàn),沒(méi)有免費(fèi)的午餐,無(wú)論采用復(fù)制還是標(biāo)記清除算法,自動(dòng)的東西都要付出很大的性能代價(jià)。
3.分代
分代是Java垃圾收集的一大亮點(diǎn),根據(jù)對(duì)象的生命周期長(zhǎng)短,把堆分為3個(gè)代:Young,Old和Permanent,根據(jù)不同代的特點(diǎn)采用不同的收集算法,揚(yáng)長(zhǎng)避短也。
Young(Nursery),年輕代。研究表明大部分對(duì)象都是朝生暮死,隨生隨滅的。因此所有收集器都為年輕代選擇了復(fù)制算法。
復(fù)制算法優(yōu)點(diǎn)是只訪問(wèn)活躍對(duì)象,缺點(diǎn)是復(fù)制成本高。因?yàn)槟贻p代只有少量的對(duì)象能熬到垃圾收集,因此只需少量的復(fù)制成本。而且復(fù)制收集器只訪問(wèn)活躍對(duì)象,對(duì)那些占了最大比率的死對(duì)象視而不見(jiàn),充分發(fā)揮了它遍歷空間成本低的優(yōu)點(diǎn)。
Young的默認(rèn)值為4M,隨堆內(nèi)存增大,約為1/15,JVM會(huì)根據(jù)情況動(dòng)態(tài)管理其大小變化。
-XX:NewRatio= 參數(shù)可以設(shè)置Young與Old的大小比例,-server時(shí)默認(rèn)為1:2,但實(shí)際上young啟動(dòng)時(shí)遠(yuǎn)低于這個(gè)比率?如果信不過(guò)JVM,也可以用-Xmn硬性規(guī)定其大小,有文檔推薦設(shè)為Heap總大小的1/4。
Young的大小非常非常重要,見(jiàn)“后面暫停時(shí)間優(yōu)先收集器”的論述。
Young里面又分為3個(gè)區(qū)域,一個(gè)Eden,所有新建對(duì)象都會(huì)存在于該區(qū),兩個(gè)Survivor區(qū),用來(lái)實(shí)施復(fù)制算法。每次復(fù)制就是將Eden和第一塊Survior的活對(duì)象復(fù)制到第2塊,然后清空Eden與第一塊Survior。Eden與Survivor的比例由-XX:SurvivorRatio=設(shè)置,默認(rèn)為32。Survivio大了會(huì)浪費(fèi),小了的話,會(huì)使一些年輕對(duì)象潛逃到老人區(qū),引起老人區(qū)的不安,但這個(gè)參數(shù)對(duì)性能并不重要。
Old(Tenured),年老代。年輕代的對(duì)象如果能夠挺過(guò)數(shù)次收集,就會(huì)進(jìn)入老人區(qū)。老人區(qū)使用標(biāo)記整理算法。因?yàn)槔先藚^(qū)的對(duì)象都沒(méi)那么容易死的,采用復(fù)制算法就要反復(fù)的復(fù)制對(duì)象,很不合算,只好采用標(biāo)記清理算法,但標(biāo)記清理算法其實(shí)也不輕松,每次都要遍歷區(qū)域內(nèi)所有對(duì)象,所以還是沒(méi)有免費(fèi)的午餐啊。
-XX:MaxTenuringThreshold=設(shè)置熬過(guò)年輕代多少次收集后移入老人區(qū),CMS中默認(rèn)為0,熬過(guò)第一次GC就轉(zhuǎn)入,可以用-XX:+PrintTenuringDistribution查看。
Permanent,持久代。裝載Class信息等基礎(chǔ)數(shù)據(jù),默認(rèn)64M,如果是類很多很多的服務(wù)程序,需要加大其設(shè)置-XX:MaxPermSize=,否則它滿了之后會(huì)引起fullgc()或Out of Memory。 注意Spring,Hibernate這類喜歡AOP動(dòng)態(tài)生成類的框架需要更多的持久代內(nèi)存。
4.minor/major collection
每個(gè)代滿了之后都會(huì)促發(fā)collection,(另外Concurrent Low Pause Collector默認(rèn)在老人區(qū)68%的時(shí)候促發(fā))。GC用較高的頻率對(duì)young進(jìn)行掃描和回收,這種叫做minor collection。
而因?yàn)槌杀娟P(guān)系對(duì)Old的檢查回收頻率要低很多,同時(shí)對(duì)Young和Old的收集稱為major collection。
System.gc()會(huì)引發(fā)major collection,使用-XX:+DisableExplicitGC禁止它,或設(shè)為CMS并發(fā)-XX:+ExplicitGCInvokesConcurrent。
5.小結(jié)
Young -- minor collection -- 復(fù)制算法
Old(Tenured) -- major colletion -- 標(biāo)記清除/標(biāo)記整理算法
三、收集器
1.古老的串行收集器(Serial Collector)
使用 -XX:+UseSerialGC,策略為年輕代串行復(fù)制,年老代串行標(biāo)記整理。
2.吞吐量?jī)?yōu)先的并行收集器(Throughput Collector)
使用 -XX:+UseParallelGC ,也是JDK5 -server的默認(rèn)值。策略為:
1.年輕代暫停應(yīng)用程序,多個(gè)垃圾收集線程并行的復(fù)制收集,線程數(shù)默認(rèn)為CPU個(gè)數(shù),CPU很多時(shí),可用–XX:ParallelGCThreads=減少線程數(shù)。
2.年老代暫停應(yīng)用程序,與串行收集器一樣,單垃圾收集線程標(biāo)記整理。
所以需要2+的CPU時(shí)才會(huì)優(yōu)于串行收集器,適用于后臺(tái)處理,科學(xué)計(jì)算。
可以使用-XX:MaxGCPauseMillis= 和 -XX:GCTimeRatio 來(lái)調(diào)整GC的時(shí)間。
3.暫停時(shí)間優(yōu)先的并發(fā)收集器(Concurrent Low Pause Collector-CMS)
前面說(shuō)了這么多,都是為了這節(jié)做鋪墊......
使用-XX:+UseConcMarkSweepGC,策略為:
1.年輕代同樣是暫停應(yīng)用程序,多個(gè)垃圾收集線程并行的復(fù)制收集。
2.年老代則只有兩次短暫停,其他時(shí)間應(yīng)用程序與收集線程并發(fā)的清除。
3.1 年老代詳述
并行(Parallel)與并發(fā)(Concurrent)僅一字之差,并行指多條垃圾收集線程并行,并發(fā)指用戶線程與垃圾收集線程并發(fā),程序在繼續(xù)運(yùn)行,而垃圾收集程序運(yùn)行于另一個(gè)個(gè)CPU上。
并發(fā)收集一開(kāi)始會(huì)很短暫的停止一次所有線程來(lái)開(kāi)始初始標(biāo)記根對(duì)象,然后標(biāo)記線程與應(yīng)用線程一起并發(fā)運(yùn)行,最后又很短的暫停一次,多線程并行的重新標(biāo)記之前可能因?yàn)椴l(fā)而漏掉的對(duì)象,然后就開(kāi)始與應(yīng)用程序并發(fā)的清除過(guò)程。可見(jiàn),最長(zhǎng)的兩個(gè)遍歷過(guò)程都是與應(yīng)用程序并發(fā)執(zhí)行的,比以前的串行算法改進(jìn)太多太多了!!!
串行標(biāo)記清除是等年老代滿了再開(kāi)始收集的,而并發(fā)收集因?yàn)橐c應(yīng)用程序一起運(yùn)行,如果滿了才收集,應(yīng)用程序就無(wú)內(nèi)存可用,所以系統(tǒng)默認(rèn)68%滿的時(shí)候就開(kāi)始收集。內(nèi)存已設(shè)得較大,吃內(nèi)存又沒(méi)有這么快的時(shí)候,可以用-XX:CMSInitiatingOccupancyFraction=恰當(dāng)增大該比率。
3.2 年輕代詳述
可惜對(duì)年輕代的復(fù)制收集,依然必須停止所有應(yīng)用程序線程,原理如此,只能靠多CPU,多收集線程并發(fā)來(lái)提高收集速度,但除非你的Server獨(dú)占整臺(tái)服務(wù)器,否則如果服務(wù)器上本身還有很多其他線程時(shí),切換起來(lái)速度就..... 所以,搞到最后,暫停時(shí)間的瓶頸就落在了年輕代的復(fù)制算法上。
因此Young的大小設(shè)置挺重要的,大點(diǎn)就不用頻繁GC,而且增大GC的間隔后,可以讓多點(diǎn)對(duì)象自己死掉而不用復(fù)制了。但Young增大時(shí),GC造成的停頓時(shí)間攀升得非常恐怖,比如在我的機(jī)器上,默認(rèn)8M的Young,只需要幾毫秒的時(shí)間,64M就升到90毫秒,而升到256M時(shí),就要到300毫秒了,峰值還會(huì)攀到恐怖的800ms。誰(shuí)叫復(fù)制算法,要等Young滿了才開(kāi)始收集,開(kāi)始收集就要停止所有線程呢。
3.3 持久代
可設(shè)置-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled,使CMS收集持久代的類,而不是fullgc,netbeans5.5 performance文檔的推薦。
4.增量(train算法)收集器(Incremental Collector)
已停止維護(hù),–Xincgc選項(xiàng)默認(rèn)轉(zhuǎn)為并發(fā)收集器。
四、暫停時(shí)間顯示
加入下列參數(shù) (請(qǐng)將PrintGC和Details中間的空格去掉,CSDN很怪的認(rèn)為是禁止字句)
-verbose:gc -XX:+PrintGC Details -XX:+PrintGCTimeStamps
會(huì)程序運(yùn)行過(guò)程中將顯示如下輸出
9.211: [GC 9.211: [ParNew: 7994K->0K(8128K), 0.0123935 secs] 427172K->419977K(524224K), 0.0125728 secs]
顯示在程序運(yùn)行的9.211秒發(fā)生了Minor的垃圾收集,前一段數(shù)據(jù)針對(duì)新生區(qū),從7994k整理為0k,新生區(qū)總大小為8128k,程序暫停了12ms,而后一段數(shù)據(jù)針對(duì)整個(gè)堆。
對(duì)于年老代的收集,暫停發(fā)生在下面兩個(gè)階段,CMS-remark的中斷是17毫秒:
[GC [1 CMS-initial-mark: 80168K(196608K)] 81144K(261184K), 0.0059036 secs]
[1 CMS-remark: 80168K(196608K)] 82493K(261184K),0.0168943 secs]
再加兩個(gè)參數(shù) -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime對(duì)暫停時(shí)間看得更清晰。
五、真正不停的BEA JRockit 與Sun RTS2.0
Bea的JRockit 5.0 R27 的特色之一是動(dòng)態(tài)決定的垃圾收集策略,用戶可以決定自己關(guān)心的是吞吐量,暫停時(shí)間還是確定的暫停時(shí)間,再由JVM在運(yùn)行時(shí)動(dòng)態(tài)決定、改變改變垃圾收集策略。
它的Deterministic GC的選項(xiàng)是-Xgcprio: deterministic,號(hào)稱可以把暫停可以控制在10-30毫秒,非常的牛,一句Deterministic道盡了RealTime的真諦。 不過(guò)細(xì)看一下文檔,30ms的測(cè)試環(huán)境是1 GB heap 和 平均 30% 的活躍對(duì)象(也就是300M)活動(dòng)對(duì)象,2 個(gè) Xeon 3.6 GHz 4G內(nèi)存 ,或者是4 個(gè)Xeon 2.0 GHz,8G內(nèi)存。
最可惜JRockt的license很奇怪,雖然平時(shí)使用免費(fèi),但這個(gè)30ms的選項(xiàng)就需要購(gòu)買整個(gè)Weblogic Real Time Server的license。
其他免費(fèi)選項(xiàng),有:
-Xgcprio:pausetime -Xpausetarget=210ms
因?yàn)槊赓M(fèi),所以最低只能設(shè)置到200ms pause target。 200ms是Sun認(rèn)為Real-Time的分界線。
-Xgc:gencon
普通的并發(fā)做法,效率也不錯(cuò)。
JavaOne2007上有Sun的 Java Real-Time System 2.0 的介紹,RTS2.0基于JDK1.5,在Real-Time Garbage Collctor上又有改進(jìn),但還在beta版狀態(tài),只供給OEM,更怪。
六、JDK 6.0的改進(jìn)
因?yàn)镴DK5.0在Young較大時(shí)的表現(xiàn)還是不夠讓人滿意,又繼續(xù)看JDK6.0的改進(jìn),結(jié)果稍稍失望,不涉及我最頭痛的年輕代復(fù)制收集改良。
1.年老代的標(biāo)識(shí)-清除收集,并行執(zhí)行標(biāo)識(shí)
JDK5.0只開(kāi)了一條收集進(jìn)程與應(yīng)用線程并發(fā)標(biāo)識(shí),而6.0可以開(kāi)多條收集線程來(lái)做標(biāo)識(shí),縮短標(biāo)識(shí)老人區(qū)所有活動(dòng)對(duì)象的時(shí)間。
2.加大了Young區(qū)的默認(rèn)大小
默認(rèn)大小從4M加到16M,從堆內(nèi)存的1/15增加到1/7
3.System.gc()可以與應(yīng)用程序并發(fā)執(zhí)行
使用-XX:+ExplicitGCInvokesConcurrent 設(shè)置
七、小結(jié)
1. JDK5.0/6.0
對(duì)于服務(wù)器應(yīng)用,我們使用Concurrent Low Pause Collector,對(duì)年輕代,暫停時(shí)多線程并行復(fù)制收集;對(duì)年老代,收集器與應(yīng)用程序并行標(biāo)記--整理收集,以達(dá)到盡量短的垃圾收集時(shí)間。
本著沒(méi)有深刻測(cè)試前不要胡亂優(yōu)化的宗旨,命令行屬性只需簡(jiǎn)單寫為:
-server -Xms<heapsize>M -Xmx<heapsize>M -XX:+UseConcMarkSweepGC -XX:+PrintGC Details -XX:+PrintGCTimeStamps
然后要根據(jù)應(yīng)用的情況,在測(cè)試軟件輔助可以下看看有沒(méi)有JVM的默認(rèn)值和自動(dòng)管理做的不夠的地方可以調(diào)整,如-xmn 設(shè)Young的大小,-XX:MaxPermSize設(shè)持久代大小等。
2. JRockit 6.0 R27.2
但因?yàn)镴DK5的測(cè)試結(jié)果實(shí)在不能滿意,后來(lái)又嘗試了JRockit,總體效果要好些。
JRockit的特點(diǎn)是動(dòng)態(tài)垃圾收集器是根據(jù)用戶關(guān)心的特征動(dòng)態(tài)決定收集算法的,參數(shù)如下
-Xms<heapsize>M -Xmx<heapsize>M -Xgcprio:pausetime -Xpausetarget=200ms -XgcReport -XgcPause -Xverbose:memory
|