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

然后要根據應用的情況,在測試軟件輔助可以下看看有沒有JVM的默認值和自動管理做的不夠的地方可以調整,如-xmn 設Young的大小,-XX:MaxPermSize設持久代大小等。
2. JRockit 6.0?R27.2
但因為JDK5的測試結果實在不能滿意,后來又嘗試了JRockit,總體效果要好些。
?JRockit的特點是動態垃圾收集器是根據用戶關心的特征動態決定收集算法的,參數如下

更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
