作者:江南白衣,最新版鏈接:http://blog.csdn.net/calvinxiu/archive/2007/05/18/1614473.aspx,版權所有,轉載請保留原文鏈接。
原本想把題目更簡(jiǎn)單的定為--《不要?!返?,但還是自己YY一下就算了。
Java開(kāi)發(fā)Server最大的障礙,就是JDK1.4版之前的的串行垃圾收集機制會(huì )引起長(cháng)時(shí)間的服務(wù)暫停,明白原理后,想想那些用JDK1.3寫(xiě)Server的先輩,不得不后怕。
好在JDK1.4已開(kāi)始支持多線(xiàn)程并行的后臺垃圾收集算法,JDK5.0則優(yōu)化了默認值的設置。
一、參考資料:
二、基本概念
1、堆(Heap)
JVM管理的內存叫堆。在32Bit操作系統上有1.5G-2G的限制,而64Bit的就沒(méi)有。
JVM初始分配的內存由-Xms指定,默認是物理內存的1/64但小于1G。
JVM最大分配的內存由-Xmx指定,默認是物理內存的1/4但小于1G。
默認空余堆內存小于40%時(shí),JVM就會(huì )增大堆直到-Xmx的最大限制,可以由-XX:MinHeapFreeRatio=指定。
默認空余堆內存大于70%時(shí),JVM會(huì )減少堆直到-Xms的最小限制,可以由-XX:MaxHeapFreeRatio=指定。
服務(wù)器一般設置-Xms、-Xmx相等以避免在每次GC 后調整堆的大小,所以上面的兩個(gè)參數沒(méi)啥用。
2.基本收集算法
可見(jiàn),沒(méi)有免費的午餐,無(wú)論采用復制還是標記清除算法,自動(dòng)的東西都要付出很大的性能代價(jià)。
3.分代
分代是Java垃圾收集的一大亮點(diǎn),根據對象的生命周期長(cháng)短,把堆分為3個(gè)代:Young,Old和Permanent,根據不同代的特點(diǎn)采用不同的收集算法,揚長(cháng)避短也。
Young(Nursery),年輕代。研究表明大部分對象都是朝生暮死,隨生隨滅的。因此所有收集器都為年輕代選擇了復制算法。
復制算法優(yōu)點(diǎn)是只訪(fǎng)問(wèn)活躍對象,缺點(diǎn)是復制成本高。因為年輕代只有少量的對象能熬到垃圾收集,因此只需少量的復制成本。而且復制收集器只訪(fǎng)問(wèn)活躍對象,對那些占了最大比率的死對象視而不見(jiàn),充分發(fā)揮了它遍歷空間成本低的優(yōu)點(diǎn)。
Young的默認值為4M,隨堆內存增大,約為1/15,JVM會(huì )根據情況動(dòng)態(tài)管理其大小變化。
-XX:NewRatio= 參數可以設置Young與Old的大小比例,-server時(shí)默認為1:2,但實(shí)際上young啟動(dòng)時(shí)遠低于這個(gè)比率?如果信不過(guò)JVM,也可以用-Xmn硬性規定其大小,有文檔推薦設為Heap總大小的1/4。
Young的大小非常非常重要,見(jiàn)“后面暫停時(shí)間優(yōu)先收集器”的論述。
Young里面又分為3個(gè)區域,一個(gè)Eden,所有新建對象都會(huì )存在于該區,兩個(gè)Survivor區,用來(lái)實(shí)施復制算法。每次復制就是將Eden和第一塊Survior的活對象復制到第2塊,然后清空Eden與第一塊Survior。Eden與Survivor的比例由-XX:SurvivorRatio=設置,默認為32。Survivio大了會(huì )浪費,小了的話(huà),會(huì )使一些年輕對象潛逃到老人區,引起老人區的不安,但這個(gè)參數對性能并不重要。
Old(Tenured),年老代。年輕代的對象如果能夠挺過(guò)數次收集,就會(huì )進(jìn)入老人區。老人區使用標記整理算法。因為老人區的對象都沒(méi)那么容易死的,采用復制算法就要反復的復制對象,很不合算,只好采用標記清理算法,但標記清理算法其實(shí)也不輕松,每次都要遍歷區域內所有對象,所以還是沒(méi)有免費的午餐啊。
-XX:MaxTenuringThreshold=設置熬過(guò)年輕代多少次收集后移入老人區,CMS中默認為0,熬過(guò)第一次GC就轉入,可以用-XX:+PrintTenuringDistribution查看。
Permanent,持久代。裝載Class信息等基礎數據,默認64M,如果是類(lèi)很多很多的服務(wù)程序,需要加大其設置-XX:MaxPermSize=,否則它滿(mǎn)了之后會(huì )引起fullgc()或Out of Memory。 注意Spring,Hibernate這類(lèi)喜歡AOP動(dòng)態(tài)生成類(lèi)的框架需要更多的持久代內存。
4.minor/major collection
每個(gè)代滿(mǎn)了之后都會(huì )促發(fā)collection,(另外Concurrent Low Pause Collector默認在老人區68%的時(shí)候促發(fā))。GC用較高的頻率對young進(jìn)行掃描和回收,這種叫做minor collection。
而因為成本關(guān)系對Old的檢查回收頻率要低很多,同時(shí)對Young和Old的收集稱(chēng)為major collection。
System.gc()會(huì )引發(fā)major collection,使用-XX:+DisableExplicitGC禁止它,或設為CMS并發(fā)-XX:+ExplicitGCInvokesConcurrent。
5.小結
Young -- minor collection -- 復制算法
Old(Tenured) -- major colletion -- 標記清除/標記整理算法
三、收集器
1.古老的串行收集器(Serial Collector)
使用 -XX:+UseSerialGC,策略為年輕代串行復制,年老代串行標記整理。
2.吞吐量?jì)?yōu)先的并行收集器(Throughput Collector)
使用 -XX:+UseParallelGC ,也是JDK5 -server的默認值。策略為:
1.年輕代暫停應用程序,多個(gè)垃圾收集線(xiàn)程并行的復制收集,線(xiàn)程數默認為CPU個(gè)數,CPU很多時(shí),可用–XX:ParallelGCThreads=減少線(xiàn)程數。
2.年老代暫停應用程序,與串行收集器一樣,單垃圾收集線(xiàn)程標記整理。
所以需要2+的CPU時(shí)才會(huì )優(yōu)于串行收集器,適用于后臺處理,科學(xué)計算。
可以使用-XX:MaxGCPauseMillis= 和 -XX:GCTimeRatio 來(lái)調整GC的時(shí)間。
3.暫停時(shí)間優(yōu)先的并發(fā)收集器(Concurrent Low Pause Collector-CMS)
前面說(shuō)了這么多,都是為了這節做鋪墊......
使用-XX:+UseConcMarkSweepGC,策略為:
1.年輕代同樣是暫停應用程序,多個(gè)垃圾收集線(xiàn)程并行的復制收集。
2.年老代則只有兩次短暫停,其他時(shí)間應用程序與收集線(xiàn)程并發(fā)的清除。
3.1 年老代詳述
并行(Parallel)與并發(fā)(Concurrent)僅一字之差,并行指多條垃圾收集線(xiàn)程并行,并發(fā)指用戶(hù)線(xiàn)程與垃圾收集線(xiàn)程并發(fā),程序在繼續運行,而垃圾收集程序運行于另一個(gè)個(gè)CPU上。
并發(fā)收集一開(kāi)始會(huì )很短暫的停止一次所有線(xiàn)程來(lái)開(kāi)始初始標記根對象,然后標記線(xiàn)程與應用線(xiàn)程一起并發(fā)運行,最后又很短的暫停一次,多線(xiàn)程并行的重新標記之前可能因為并發(fā)而漏掉的對象,然后就開(kāi)始與應用程序并發(fā)的清除過(guò)程??梢?jiàn),最長(cháng)的兩個(gè)遍歷過(guò)程都是與應用程序并發(fā)執行的,比以前的串行算法改進(jìn)太多太多了?。?!
串行標記清除是等年老代滿(mǎn)了再開(kāi)始收集的,而并發(fā)收集因為要與應用程序一起運行,如果滿(mǎn)了才收集,應用程序就無(wú)內存可用,所以系統默認68%滿(mǎn)的時(shí)候就開(kāi)始收集。內存已設得較大,吃?xún)却嬗譀](méi)有這么快的時(shí)候,可以用-XX:CMSInitiatingOccupancyFraction=恰當增大該比率。
3.2 年輕代詳述
可惜對年輕代的復制收集,依然必須停止所有應用程序線(xiàn)程,原理如此,只能靠多CPU,多收集線(xiàn)程并發(fā)來(lái)提高收集速度,但除非你的Server獨占整臺服務(wù)器,否則如果服務(wù)器上本身還有很多其他線(xiàn)程時(shí),切換起來(lái)速度就..... 所以,搞到最后,暫停時(shí)間的瓶頸就落在了年輕代的復制算法上。
因此Young的大小設置挺重要的,大點(diǎn)就不用頻繁GC,而且增大GC的間隔后,可以讓多點(diǎn)對象自己死掉而不用復制了。但Young增大時(shí),GC造成的停頓時(shí)間攀升得非??植?,比如在我的機器上,默認8M的Young,只需要幾毫秒的時(shí)間,64M就升到90毫秒,而升到256M時(shí),就要到300毫秒了,峰值還會(huì )攀到恐怖的800ms。誰(shuí)叫復制算法,要等Young滿(mǎn)了才開(kāi)始收集,開(kāi)始收集就要停止所有線(xiàn)程呢。
3.3 持久代
可設置-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled,使CMS收集持久代的類(lèi),而不是fullgc,netbeans5.5 performance文檔的推薦。
4.增量(train算法)收集器(Incremental Collector)
已停止維護,–Xincgc選項默認轉為并發(fā)收集器。
四、暫停時(shí)間測試
加入下列參數 (請將PrintGC和Details中間的空格去掉,CSDN很怪的認為是禁止字句)

會(huì )程序運行過(guò)程中將顯示如下輸出
9.211: [GC 9.211: [ParNew: 7994K->0K(8128K), 0.0123935 secs] 427172K->419977K(524224K), 0.0125728 secs]
顯示在程序運行的9.211秒發(fā)生了Minor的垃圾收集,前一段數據針對新生區,從7994k整理為0k,新生區總大小為8128k,程序暫停了12ms,而后一段數據針對整個(gè)堆。
對于年老代的收集,暫停發(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è)參數 -XX:+PrintGCApplicationConcurrentTime -XX:+PrintGCApplicationStoppedTime對暫停時(shí)間看得更清晰。
五、小結
對于服務(wù)器應用,我們使用JDK5的Concurrent Low Pause Collector,對年輕代,暫停時(shí)多線(xiàn)程并行復制收集;對年老代,收集器與應用程序并行標記--整理收集,以達到盡量短的垃圾收集時(shí)間。
本著(zhù)沒(méi)有深刻測試前不要胡亂優(yōu)化的宗旨,命令行屬性只需簡(jiǎn)單寫(xiě)為:

然后要根據應用的情況,在測試軟件輔助可以下看看有沒(méi)有JVM的默認值和自動(dòng)管理做的不夠的地方可以調整,如-xmn 設Young的大小,-XX:MaxPermSize設持久代大小等。
六、真正不停的BEA JRockit 與Sun RTS2.0
Bea的JRockit 5.0 R27 有Deterministic GC的選項-Xgcprio: deterministic,通過(guò)動(dòng)態(tài)的垃圾收集機制而不是等代滿(mǎn)了才收集,號稱(chēng)可以把暫??梢钥刂圃?0-30毫秒,非常的牛,一句Deterministic道盡了RealTime的真諦。而JDK6如果上到2G Heap、64MYoung、4CPU、8G內存,就鐵定是幾百ms上上下下。
細看一下文檔,30ms的測試環(huán)境是1 GB heap 和 平均 30% 的活躍對象(也就是300M)活動(dòng)對象,2 個(gè) Xeon 3.6 GHz 4G內存 ,或者是4 個(gè)Xeon 2.0 GHz,8G內存。
可惜JRockt的license很奇怪,雖然平時(shí)使用免費,但這個(gè)30ms的選項就需要購買(mǎi)整個(gè)Weblogic Real Time Server的license。 其他免費選項,最低只能設置到200ms pause target。
JavaOne2007上有Sun的 Java Real-Time System 2.0 的介紹,RTS2.0基于JDK1.5,在Real-Time Garbage Collctor上又有改進(jìn),但還在beta版狀態(tài),而且也是要錢(qián)的。
七、JDK 6.0的改進(jìn)
因為JDK5.0在Young較大時(shí)的表現還是不夠讓人滿(mǎn)意,又繼續看JDK6.0的改進(jìn),結果稍稍失望,不涉及我最頭痛的年輕代復制收集改良。
1.年老代的標識-清除收集,并行執行標識
JDK5.0只開(kāi)了一條收集進(jìn)程與應用線(xiàn)程并發(fā)標識,而6.0可以開(kāi)多條收集線(xiàn)程來(lái)做標識,縮短標識老人區所有活動(dòng)對象的時(shí)間。
2.加大了Young區的默認大小
默認大小從4M加到16M,從堆內存的1/15增加到1/7
3.System.gc()可以與應用程序并發(fā)執行
使用-XX:+ExplicitGCInvokesConcurrent 設置
聯(lián)系客服