Java編程是內(nèi)存受限模式下的編程, 內(nèi)存對(duì)于 Java 程序來(lái)說(shuō),是非常有限的資源,所以了解Garbage Collection的機(jī)制很重要。
我們只要加入java的啟動(dòng)參數(shù) -verbose:gc,就可以用查看垃圾回收的情況。
下面是有關(guān)參數(shù)的幾個(gè)說(shuō)明
-verbose:gc在虛擬機(jī)發(fā)生內(nèi)存回收時(shí)在輸出設(shè)備顯示信息,該參數(shù)用來(lái)監(jiān)視虛擬機(jī)內(nèi)存回收的情況。
-Xnoclassgc
關(guān)閉虛擬機(jī)對(duì)class的垃圾回收功能。
-Xincgc
啟動(dòng)增量垃圾收集器,缺省是關(guān)閉的。增量垃圾收集器能減少偶然發(fā)生的長(zhǎng)時(shí)間的垃圾回收造成的暫停時(shí)間。但增量垃圾收集器和應(yīng)用程序并發(fā)執(zhí)行,因此會(huì)占用部分CPU在應(yīng)用程序上的功能。
-Xloggc:<file>
將虛擬機(jī)每次垃圾回收的信息寫(xiě)到日志文件中,文件名由file指定,文件格式是平文件,內(nèi)容和-verbose:gc輸出內(nèi)容相同。
JVM管理的內(nèi)存,通常叫做堆(heap),如果 JVM 不能在 java 堆中獲得更多內(nèi)存來(lái)分配更多 java 對(duì)象,將會(huì)拋出 java 內(nèi)存不足錯(cuò)誤。如果 java 堆充滿了活動(dòng)對(duì)象,并且 JVM 無(wú)法再擴(kuò)展 java 堆,那么它將不能分配更多 java 對(duì)象。
在這種情況下,JVM就出現(xiàn) java.lang.OutOfMemoryError 。
JVM啟動(dòng)后,保留一段地址空間,這個(gè)空間的大小由-Xmx指定。這塊空間的大小就是heap可能的最大值,但一開(kāi)始不一定全都分配了物理內(nèi)存,初始分配的heap大小由-Xms指定,一般的jdk都有最小值,如果-Xms小于-Xmx,需要的時(shí)候,向OS申請(qǐng)
當(dāng)JVM進(jìn)行GC的時(shí)候,是要消耗CPU資源和需要一定時(shí)間的,這會(huì)影響到程序的正常運(yùn)行,因此需要盡可能減少GC消耗的時(shí)間。
Java程序運(yùn)行過(guò)程中,對(duì)象的生命周期有長(zhǎng)有短,所以Java Heap分為3個(gè)區(qū),Young,Old和Permanent。當(dāng)Young被填滿時(shí),GC會(huì)將對(duì)象移到Old區(qū)。Permanent是一個(gè)永久區(qū)域,分配給JVM,可以通過(guò) 命令行參數(shù) -XX:MaxPermSize=64m 來(lái)設(shè)置,permanent generation添滿后導(dǎo)致Full GC。
young generation由一塊Eden和兩塊Survivor Space構(gòu)成。新創(chuàng)建的對(duì)象的內(nèi)存都分配自eden。一塊Survivor Space是空閑的,用作copying collection的目標(biāo)空間。Minor collection的過(guò)程就是將eden和在用survivor space中的活對(duì)象copy到空閑survivor space中。對(duì)象在survivor spaces 里經(jīng)歷了數(shù)次copied后,就會(huì)被移到tenured generation或tenured掉。
GC用較高的頻率對(duì)young generation進(jìn)行掃描和回收,這種叫做minor collection,而對(duì)old generation的檢查回收頻率要低很多,稱為major collection。這樣就不需要每次GC都將內(nèi)存中所有對(duì)象都檢查一遍。
老版本的JVM是分代垃圾收集(generational garbage collection),有兩種收集方式:
第一個(gè)是scavenge,將所有活的對(duì)象搬到另外一塊內(nèi)存后,整塊內(nèi)存回收。這種方法有效率,但需要有一定的空閑內(nèi)存,拷貝也有開(kāi)銷(xiāo),用于minor collection。
第二個(gè)是mark-compact,將活的對(duì)象標(biāo)記出來(lái),然后搬遷到一起連成大塊的內(nèi)存,其他內(nèi)存就可以回收了。這種方法不需要額外的空間,速度會(huì)慢一些,方法用于major collection。
注意這兩種GC機(jī)制都不是并行的,是單線程的,GC運(yùn)行時(shí),所有的用戶線程將暫停,Java應(yīng)用程序不做任何工作,web應(yīng)用停止響應(yīng)。
JDK 1.4.1 中新的收集器都是為解決多處理器系統(tǒng)中垃圾收集器的問(wèn)題而設(shè)計(jì)的。因?yàn)榇蠖鄶?shù)垃圾收集算法會(huì)在一段時(shí)間里使系統(tǒng)停止,單線程的收集器很快會(huì)成為伸縮性瓶頸,因?yàn)樵诶占鲗⒂脩舫绦蚓€程掛起時(shí),除了一個(gè)處理器之外,其他的處理器都是空閑的。新收集器中的兩個(gè)――并行復(fù)制收集器和并發(fā)標(biāo)記-清除收集器――設(shè)計(jì)為減少收集暫停時(shí)間。另一個(gè)是并行清除收集器,它是為在大堆上的更高吞吐能力而設(shè)計(jì)的。
并行復(fù)制收集器用 JVM 選項(xiàng) -XX:+UseParNewGC 啟用,是一個(gè)年輕代復(fù)制收集器,它將垃圾收集的工作分為與 CPU 數(shù)量一樣多的線程。并發(fā)標(biāo)記-清除收集器由 -XX:+UseConcMarkSweepGC 選項(xiàng)啟用,它是一個(gè)老代標(biāo)記-清除收集器,它在初始標(biāo)記階段(及在以后暫短重新標(biāo)記階段)暫短地停止整個(gè)系統(tǒng),然后恢復(fù)用戶程序,同時(shí)垃圾收集器線程與用戶程序并發(fā)地執(zhí)行。并行復(fù)制收集器和并發(fā)標(biāo)記-清除收集器基本上是默認(rèn)的復(fù)制收集器和標(biāo)記-整理收集器的并發(fā)版本。由 -XX:+UseParallelGC 啟用的并行清除收集器是年輕代收集器,針對(duì)多處理器系統(tǒng)上非常大(吉字節(jié)以及更大的)堆進(jìn)行了優(yōu)化。
JDK 1.4.1 還包括大量的微調(diào)垃圾收集的選項(xiàng)。調(diào)整這些選項(xiàng)并衡量它們的效果可能會(huì)花費(fèi)您大量時(shí)間,因此在試圖微調(diào)垃圾收集器之前先對(duì)您的應(yīng)用程序進(jìn)行徹底的配置(profile)和優(yōu)化,這樣您的微調(diào)工作可能會(huì)得到更好的結(jié)果。
微調(diào)垃圾收集首先要做的是檢查冗長(zhǎng)的 GC 輸出。這會(huì)使您得到垃圾收集操作的頻率、定時(shí)和持續(xù)時(shí)間等信息。最簡(jiǎn)單的垃圾收集微調(diào)就是擴(kuò)大最大堆的大小( -Xmx )。隨著堆的增大,復(fù)制收集會(huì)變得更有效,所以在增大堆時(shí),您就減少了每個(gè)對(duì)象的收集成本。除了增加最大堆的大小,還可以用選項(xiàng) -XX:NewRatio 增加分配給年輕代的空間份額。也可以用 -Xmn 選項(xiàng)顯式指定年輕代的大小。
注意:大的收集(major collection)既會(huì)收集年輕的代,也會(huì)收集老的代。 System.gc() 方法總是觸發(fā)一個(gè)大的收集,這就是應(yīng)該盡量少用(如果不能完全不用的話) System.gc() 的原因之一,因?yàn)榇蟮氖占刃〉氖占ㄙM(fèi)長(zhǎng)得多的時(shí)間。沒(méi)有辦法以編程方式觸發(fā)小的收集,最好使用-XX:+DisableExplicitGC禁止ExplicitGC。
JVM的很多參數(shù)會(huì)影響里面各部分空間的分配。-XX:MinHeapFreeRatio與-XX:MaxHeapFreeRatio設(shè)定空閑內(nèi)存占總內(nèi)存的比例范圍,這兩個(gè)參數(shù)會(huì)影響GC的頻率和單次GC的耗時(shí)。-XX:NewRatio決定young與old generation的比例,設(shè)置-XX:NewRatio=3意味著old和 young generation是1:3。Young generation空間越大,minor collection頻率越低,但是old generation空間小了,又可能導(dǎo)致major collection頻率增加。一般來(lái)說(shuō),eden大小在maximum heap大小的1/4到1/3之間,old generation的大小一定要大于young generation。
-XX:NewSize和-XX:MaxNewSize直接指定了young generation的最小值和最大值。
如果是Server-Side應(yīng)用,請(qǐng)加 -server 參數(shù)。這樣,缺省的NewRatio 是2,SurvivorRatio 是 25 ,適合大部分應(yīng)用。也可以用NewSize、MaxNewSize來(lái)設(shè)置。 設(shè)置- Xms 和 -Xmx 的大小相等,可以避免在每次 GC 后調(diào)整堆內(nèi)存的大小。同樣道理設(shè)置NewSize、MaxNewSize相等。
對(duì)于web應(yīng)用還要注意,由于經(jīng)常生成和加載類(lèi),permanent generation對(duì)GC的影響較大,應(yīng)設(shè)置的大些。如果應(yīng)用程序中有長(zhǎng)期活對(duì)象,盡可能減少這些對(duì)象的存在期。例如,調(diào)整 HTTP 會(huì)話超時(shí)值將有助于更快地回收空閑會(huì)話對(duì)象。存泄漏的一個(gè)例子是在應(yīng)用服務(wù)器中使用數(shù)據(jù)庫(kù)連接池。當(dāng)使用連接池時(shí),必須在 finally 塊中顯式關(guān)閉 JDBC 語(yǔ)句和結(jié)果集對(duì)象。這是因?yàn)椋?dāng)從池中調(diào)用連接對(duì)象上的 close() 時(shí),只是簡(jiǎn)單地把連接返回池中以供重用,并沒(méi)有實(shí)際關(guān)閉連接和關(guān)聯(lián)的語(yǔ)句/結(jié)果集對(duì)象。如果使用全局緩存,最好使用 Week Reference,例如用 WeakHashMap 代替 HashMap 。
posted on 2010-10-28 14:45
Daniel 閱讀(793)
評(píng)論(0) 編輯 收藏 所屬分類(lèi):
CoreJava