誰(shuí)需要GC調(diào)優(yōu) 小規(guī)模程序,垃圾收集算法能很好的工作。因?yàn)槔锩娴膶?duì)象圖不大,所以收集代價(jià)不高。但是如果是大規(guī)模的程序,對(duì)象成百上千,一次遍歷,就算是復(fù)制收集器中只對(duì)活動(dòng)對(duì)象的遍歷,都需要很長(zhǎng)的時(shí)間。所以,大規(guī)模程序,有必要深入了解GC的工作方式,了解和調(diào)整GC的參數(shù)。 關(guān)于串行和并行GC收集算法 在JVM 1.3.1以前,只有串行收集器,沒(méi)有并行收集器,對(duì)于多處理器,系統(tǒng)吞吐量損失很大。見(jiàn)下圖。為此,1.3.1以后(不包含1.3.1),引入了并發(fā)GC收集算法。 JVM1.4.2上,有4種垃圾收集器,默認(rèn)會(huì)選擇串行收集器。 JVM5.0后,則會(huì)根據(jù)用戶機(jī)器的類(lèi)型自動(dòng)選擇收集器。 
1% GC 中的 1%是指,在一個(gè)CPU上執(zhí)行且只花1%的CPU時(shí)間做垃圾收集的應(yīng)用程序。 可以看到,當(dāng)這個(gè)程序在處理器為30個(gè)以上的系統(tǒng)上運(yùn)行,系統(tǒng)的吞吐量會(huì)下降到80%以下,即本來(lái)在單CPU系統(tǒng)上只花1%時(shí)間做搜集的程序會(huì)在30個(gè)以上CPU的系統(tǒng)上,花上20%的時(shí)間做收集。 為什么會(huì)這樣呢?因?yàn)閱蜟PU收集算法在多CPU系統(tǒng)上面做收集的時(shí)候,GC算法會(huì)暫停運(yùn)行在任何CPU上面的程序,而自己卻只能利用一個(gè)CPU做GC,所以造成了這種情況。
代Generation
自從JVM1.2以后,Sun采用了一種分代收集的策略,即將堆分成3個(gè)不同的區(qū)域,按照對(duì)象存活的時(shí)間的不同,將對(duì)象保存在不同的堆上。在不同的代上面,應(yīng)用不同的收集算法,來(lái)達(dá)到最優(yōu)化收集。
這3個(gè)區(qū)域叫年輕代,年老代和持久代。 除Class和Method等元數(shù)據(jù)在持久代上面分配以外,所有的對(duì)象都在年輕代上面分配,當(dāng)達(dá)到一定的條件,如經(jīng)過(guò)在年輕代上面N次收集后的對(duì)象,就會(huì)保存到年老代之中。
在年輕代上面進(jìn)行的收集叫Minor collections,在年輕代和年老代上面同時(shí)收集叫Major collections。
Tips: 盡量不要調(diào)用System.gc(),因?yàn)檫@會(huì)觸發(fā)Major collections。Major collections的收集效率不高,因?yàn)樗闅v幾乎所有的對(duì)象。 沒(méi)有辦法利用API直接觸發(fā)Minor collections,但是仍然有其他的調(diào)優(yōu)手段。當(dāng)使用完集合對(duì)象后,把引用設(shè)置為null,這樣避免gc在收集過(guò)程中,無(wú)謂地遍歷那些即將就要釋放的對(duì)象。
實(shí)驗(yàn)表明,年輕代上面的對(duì)象98%的對(duì)象都會(huì)在短時(shí)間內(nèi)死亡,故Minor collections可以利用拷貝收集器,只遍歷那些2%存活的對(duì)象,而不用管那些死亡的對(duì)象,來(lái)提高收集效率。 為了讓Minor collections能充分利用年輕代上面對(duì)象大量死亡的這個(gè)特點(diǎn),就需要調(diào)整以下幾個(gè)參數(shù): 1. 收集的頻率 過(guò)于頻繁的收集,會(huì)導(dǎo)致代中對(duì)象死亡率不夠高,從而需要遍歷這個(gè)代中大部分的對(duì)象,使得高死亡率這個(gè)條件利用的不充分。
2. 年輕代(堆)的大小 如果堆太小,一旦堆被占滿,Minor collections就不得不頻繁的啟動(dòng),導(dǎo)致情況1的發(fā)生,從而降低收集效率。
下圖反應(yīng)了絕大多數(shù)對(duì)象在早起死亡的這一個(gè)事實(shí): 
從圖中可以看到,隨著時(shí)間的推進(jìn),大部分分配的字節(jié)都被回收了,少部分留了下來(lái)。被回收的這些字節(jié),就是所謂的die young,留下的則是live longer。
Bytes allocated 已經(jīng)分配的總字節(jié)數(shù) Bytes Surviving 存活的字節(jié)數(shù) Minor Collections 對(duì)年輕代進(jìn)行收集 Major Collections 對(duì)所有代進(jìn)行收集 下圖描述了JVM中對(duì)堆的劃分: 
Young 年輕代 Tenured 年老代 Perm 持久代
Virutal是指保留,而未分配的內(nèi)存。如Perm中,加上Virtual則是Perm區(qū)域最大的大小,而剛開(kāi)始并不會(huì)完全分配這個(gè)堆,只會(huì)按照最小的大小分配。
Young部分,被分為了三個(gè)部分,一個(gè)Eden,和兩個(gè)大小相同的Survivor。 所有的新建對(duì)象都會(huì)在Eden中分配,當(dāng)Eden占滿后,即剩余的大小不足以分配新的對(duì)象時(shí),就會(huì)觸發(fā)Minor collections。對(duì)Young收集時(shí),會(huì)將對(duì)象拷貝到其中一個(gè)Survivor,另外一個(gè)Survivor保留不用。當(dāng)下次收集時(shí),則將上次Survivor中和Eden中的活動(dòng)對(duì)象,拷貝到未用的Survivor中。如此反復(fù)。當(dāng)某些對(duì)象經(jīng)歷足夠長(zhǎng)的次數(shù)或者時(shí)間后,就會(huì)被拷貝入年老代。 如果對(duì)Young的minor collections收集到的活動(dòng)對(duì)象,survivor無(wú)法完全容納,則會(huì)將某些對(duì)象拷貝到年老代,如果年老代也不能容納新拷貝入的對(duì)象,則觸發(fā)Major collections。如果Major collections后,如果還不足以容納,就會(huì)將Virutal中預(yù)留的空間用來(lái)擴(kuò)展已有的堆。當(dāng)保留Virutal分配完畢后,仍然不足時(shí),就會(huì)拋出OutOfMemory的錯(cuò)誤。
Tips: 不要把年老代設(shè)置的過(guò)小,一般最好能比eden+survivor更大一些,這樣可以避免觸發(fā)Major collections。在這個(gè)前提下,年輕代越大越好。由于Young的eden區(qū)域是拷貝收集,容易產(chǎn)生碎片,所以此區(qū)域越大,越不容易導(dǎo)致因?yàn)樗槠瑢?dǎo)致的內(nèi)存不足而引發(fā)的minor collections。至于survivor,則要根據(jù)情況調(diào)整。過(guò)大的survior會(huì)造成浪費(fèi),過(guò)小的survior會(huì)導(dǎo)致,對(duì)象被直接拷貝到年老代。 JVM1.2中,未使用上面一大二小的結(jié)構(gòu),而是將Young分成兩個(gè)相同大小的區(qū)域,來(lái)回進(jìn)行拷貝。 調(diào)整GC的手段 
如何看懂上面那張圖: 行,分別對(duì)應(yīng)了三個(gè)代 列,分別對(duì)應(yīng)了實(shí)時(shí)性要求比較高的默認(rèn)的和可調(diào)節(jié)的選項(xiàng), 以及對(duì)吞吐量比較高的默認(rèn)的和可調(diào)節(jié)的選項(xiàng),還有就是調(diào)整這3個(gè)堆的選項(xiàng)。 
-Xmx 調(diào)整JVM啟動(dòng)時(shí),保留Total Size的大小 -Xms 調(diào)整JVM啟動(dòng)初始化時(shí),Total Size的大小
每一次收集后,都會(huì)根據(jù)以下兩個(gè)參數(shù)調(diào)整Total Size的大小
-XX:MinHeapFreeRatio(默認(rèn),40) Total Size中可用空間小于這個(gè)比率,就會(huì)擴(kuò)展堆的大小,保持這個(gè)值 -XX:MaxHeapFreeRatio(默認(rèn),70) Total Size中可用空間大于這個(gè)比例,就縮小堆的大小,保持這個(gè)值 Tips: 如果你的程序是服務(wù)器,那么通過(guò)將-Xmx和-Xms設(shè)置成相近,或者相同,可以阻止這種堆大小的頻繁調(diào)整,造成的不必要的收集和堆增長(zhǎng)過(guò)程。 -XX:NewRatio=3 年老代和年輕代的內(nèi)存分配比率,3表示,在Total Size中,年輕代占1份,年老代3份 -XX:NewSize 年輕代初始化時(shí)的大小 -XX:MaxNewSize 如果不指定,那么年輕代可以增長(zhǎng)不受限制,但受NewRatio的限制 -XX:SurvivorRato=6 年輕代中,eden與survivor的比率,這里eden占6份,2個(gè)survivor占2份 -XX:MaxTenuringThreshold=0 閥值,超過(guò)這個(gè)閥值的對(duì)象將被拷貝到年老代 如何進(jìn)行GC調(diào)優(yōu)? 1.性能的幾個(gè)重要度量參數(shù) Troughput 吞吐量,除掉GC所用的時(shí)間后,真正執(zhí)行程序所在總時(shí)間的百分比 Pauses 暫停時(shí)間,即由于正在做GC,而沒(méi)有響應(yīng)的那些時(shí)間 Footprint 內(nèi)存需求,通常用page和cache line的數(shù)量來(lái)衡量
2.查看當(dāng)前的GC收集 java命令運(yùn)行時(shí),輸入 -verbose:gc 參數(shù)
[GC 4802K->4383K(5312K), 0.0078566 secs] //執(zhí)行了Minor collections [Full GC 4383K->4383K(5312K), 0.0385521 secs] //執(zhí)行了Major collections [GC 5248K->4814K(8216K), 0.0121798 secs] //空間仍然不夠,擴(kuò)展了年輕代和年老代的空間 以第一行為例 GC 表示執(zhí)行了Minor collections 4802K->4383K 表示GC執(zhí)行前和執(zhí)行后,堆中活動(dòng)對(duì)象的大小 (5312K) 表示總的堆的大小(不算持久代,而且只算2個(gè)Survivor中的1個(gè),即用戶可用堆的大?。?br>0.0078566 secs 表示GC所用的時(shí)間。主要,也是首先看這個(gè),然后再看GC/Full GC是否過(guò)于頻繁。 這個(gè)信息反應(yīng)了什么?年輕代太小,因?yàn)閳?zhí)行了Minor collections之后,活動(dòng)的對(duì)象并沒(méi)有顯著減少,4802K->4383K, 說(shuō)明初始堆分配的不夠大。這個(gè)GC顯然是因?yàn)閑den內(nèi)部碎片導(dǎo)致的。 用-XX:+PrintGCDetails 參數(shù),打印更詳細(xì)的信息
[GC [DefNew: 64575K->959K(64576K), 0.0457646 secs] 196016K->133633K(261184K), 0.0459067 secs]] // Minor collections從年輕代64575K中收集到了959K的活動(dòng)對(duì)象,花費(fèi)了4微秒多一點(diǎn) DefNew: 64575K->959K(64576K), 0.0457646 secs //整個(gè)堆整理后,從196016K中,收集到了133633K的活動(dòng)對(duì)象 196016K->133633K(261184K) 還可以用-XX:+PrintGCTimeStamps 查看帶起始和終止時(shí)間戳的信息 還有-XX:-PrintTenuringDistribution 打印出對(duì)象在放入年老代之前在年輕代做了多少次復(fù)制,如果復(fù)制次數(shù)過(guò)少,說(shuō)明年輕代過(guò)小。 2類(lèi)收集器 The Throughput Collector 以提高吞吐量,降低GC時(shí)間比的收集策略 The Concurrent Low Pause Collector 以提高暫停時(shí)間,以實(shí)時(shí)性為目的的收集策略 具體請(qǐng)看資料[1]中的內(nèi)容。 其他 關(guān)于,如何遍歷年輕代中的活動(dòng)對(duì)象的技術(shù),請(qǐng)看資料[3]。 參考資料:
1.Tuning Garbage Collection with the 5.0 Java[tm] Virtual Machine http://java./docs/hotspot/gc5.0/gc_tuning_5.html
2.Garbage Collector Ergonomics http://java./j2se/1.5.0/docs/guide/vm/gc-ergonomics.html
3.JVM1.4.1中的垃圾收集 http://www.ibm.com/developerworks/cn/java/j-jtp11253/
|