基于WebSphere 構(gòu)建的企業(yè)應(yīng)用,時常會出現(xiàn)性能問題,在嚴(yán)重的情況下還會提示出內(nèi)存溢出,這是一件很讓人惱怒的事情。在WebSphere Application Server(Was)運(yùn)行的時候,內(nèi)存溢出,會生成大量的溢出文件,如Javacore, Heapdump等文件,占用了大量的磁盤空間。在這種情況下,時常會出現(xiàn)一連串的系統(tǒng)問題,如部署在Was的所有應(yīng)用服務(wù)都報錯,Was連控制臺也無法訪問等。
為解決問題,我們通常會選擇重新啟動整個Was或者服務(wù)器,然后分析運(yùn)行日志SystemOut.log、ystemErr.log、ative_stdout.log、native_stderr.log 和系統(tǒng)內(nèi)存溢出的時候產(chǎn)生的Javacore、Heapdump文件來尋找出問題。
那么,為什么會出現(xiàn)內(nèi)存溢出呢?
應(yīng)用服務(wù)器在運(yùn)行過程中需要創(chuàng)建很多對象,而在應(yīng)用服務(wù)器的堆空間大小有限的情況下,請求進(jìn)程不斷申請空間來創(chuàng)建與存放對象,在達(dá)到上限時而服務(wù)器又沒能釋放出空間來處理申請空間的請求就會出現(xiàn)內(nèi)存溢出情況。這就像吹氣球,當(dāng)氣球中的氣體到達(dá)一定程度時,氣球就會被撐爆。(32位的JDK的Jvm堆空間分配最大支持1.5G的大小,超過則無法正常啟動。而64位的JDK堆大小分配無限制,其大小受到服務(wù)器的內(nèi)存限制。)通常在投入生產(chǎn)的系統(tǒng)中,出現(xiàn)溢出一般都是對象分配不合理導(dǎo)致的。
在此,讓我們先了解下Java世界里,對象與對象管理是怎么一回事。
在Java的體系中,所有的類作為一個對象(包括Jdk本身提供的類,應(yīng)用中由開發(fā)人員編寫的類),都是直接或者間接繼承了Java.lang.object產(chǎn)生的。這些類被創(chuàng)建的時候都會向Jvm堆申請一定的內(nèi)存空間存放,因此在Jvm堆空間里會存放各式各樣的對象,有的是靜態(tài)類型,有的是私有類型等等,而這些對象都是通過垃圾收集器進(jìn)行管理的。
垃圾收集器(Garbage Collection,我們通常稱之為GC),它是Java與C++語言的一個顯著的不同點。Java語言通過垃圾收集器管理內(nèi)存中的對象,垃圾收集器是在C++基礎(chǔ)上的一種極大進(jìn)步,使許多編程問題消失于無形中。通過垃圾回收,在Java編程過程中,內(nèi)存漏洞的情況會少得多。
雖然垃圾收集器為開發(fā)帶來了極大的便利,但如果對象分配與使用不合理,也會導(dǎo)致垃圾回收在性能上拖Jvm的后腿。為了解決性能問題,GC則是一個讓我們不能忽視的問題。根據(jù)不同的應(yīng)用場景,選擇適合的GC策略,可以幫助系統(tǒng)性能在一定程度上得到顯著的優(yōu)化。
垃圾收集器為了快速清除在JVM堆中不再使用的對象,從而釋放出空間來供其他請求創(chuàng)建對象,常見的方法有引用計數(shù)方法和對象引用遍歷方法等算法,目前主流的方法為對象引用遍歷方法。通過這些高效的算法實現(xiàn)的GC策略有許多種,他們都是針對各種應(yīng)用場景而產(chǎn)生的,我們可以通過在JVM啟動參數(shù)中設(shè)置,來調(diào)整GC策略。
在IBM SDK 5.0提供了四種不同的GC策略優(yōu)化配置(IBM 在WebSphere 6.1版本開始,IBM JDK 升級到IBM SDK5.0,也就是常說的JDK1.5),詳細(xì)如下:
序號
策略
選項
描述
備注
1
針對吞吐量進(jìn)行優(yōu)化
-Xgcpolicy:optthruput
WAS默認(rèn)策略。對于吞吐量比短暫的 GC 停頓更重要的應(yīng)用程序,通常使用這種策略。每當(dāng)進(jìn)行垃圾收集時,應(yīng)用程序都會停頓。
2
針對停頓時間進(jìn)行優(yōu)化
-Xgcpolicy:optavgpause
通過并發(fā)地執(zhí)行一部分垃圾收集,在高吞吐量和短 GC 停頓之間進(jìn)行折中。應(yīng)用程序停頓的時間更短。
3
分代并發(fā)
-Xgcpolicy:gencon
以不同方式處理短期存活的對象和長期存活的對象。采用這種策略時,具有許多短期存活對象的應(yīng)用程序會表現(xiàn)出更短的停頓時間,同時仍然產(chǎn)生很好的吞吐量。
推薦使用
4
子池
-Xgcpolicy:subpool
采用與默認(rèn)策略相似的算法,但是采用一種比較適合多處理器計算機(jī)的分配策略。建議對于有 16 個或更多處理器的 SMP 計算機(jī)使用這種策略。這種策略只能在 IBM pSeries® 和 zSeries® 平臺上使用。需要擴(kuò)展到大型計算機(jī)上的應(yīng)用程序可以從這種策略中受益。
在Sun Jvm也有自己特色的GC策略,如:
序號
策略
選項
描述
備注
1
并發(fā)收集器
-XX:+UseConcMarkSweepGC
并發(fā)收集器與應(yīng)用程序同時運(yùn)行。這些收集器在某點上(比如壓縮時),一般都不得不停止其他操作,以完成特定的任務(wù),但是因為其他應(yīng)用程序可進(jìn)行其他的后臺操作,所以中斷其他處理的實際時間大大降低。
2
并行收集器
-XX:+UseParallelGC
并行收集器使用某種傳統(tǒng)的算法,并使用多線程并行地執(zhí)行它們的工作。在多cpu機(jī)器上使用多線程技術(shù)可以顯著的提高java應(yīng)用程序區(qū)性的可擴(kuò)展性。
3
串行收集器
-XX:+UseSerialGC
用單線程處理所有垃圾回收工作,因為無需多線程交互,所以效率比較高。但是,也無法使用多處理器的優(yōu)勢,所以此收集器適合單處理器機(jī)器。當(dāng)然,此收集器也可以用在小數(shù)據(jù)量(100M左右)情況下的多處理器機(jī)器上。
通過以上的列表數(shù)據(jù),大家也接觸到了各種常見的GC策略,相信到此對GC與GC策略也有了一定程度的了解。那如何衡量GC的性能,如何為何業(yè)務(wù)應(yīng)用選擇合適的GC策略呢?
一般來說,衡量GC效率的參數(shù)主要有2類,吞吐量和停頓時間。
吞吐量是應(yīng)用程序處理的數(shù)據(jù)量。衡量吞吐量的標(biāo)準(zhǔn)是與具體應(yīng)用程序相關(guān)的。
停頓時間是垃圾收集器將所有應(yīng)用程序線程停下來,從而對堆進(jìn)行收集所經(jīng)歷的時間。
如何分析和選擇適合的GC策略呢?我們可以在業(yè)務(wù)應(yīng)用系統(tǒng)打開詳細(xì)垃圾回收,收集系統(tǒng)在運(yùn)行過程中的GC日志,通過分析GC日志,來判斷當(dāng)前GC策略是否符合當(dāng)前應(yīng)用系統(tǒng)。
在實際應(yīng)用場景中,不是每個應(yīng)用服務(wù)都必須調(diào)整GC,如果您寄予厚望希望調(diào)了GC策略后,就一定能使系統(tǒng)達(dá)到性能上的提升,成效未必盡入人意。因此GC策略調(diào)整只是從GC角度去輔助性能優(yōu)化,而調(diào)優(yōu)的效果需要根據(jù)實際情況才能斷定。例如:在測試機(jī)調(diào)整了GC策略,使用壓力測試工具進(jìn)行壓力測試,性能優(yōu)化成效顯著;但在生產(chǎn)系統(tǒng)上調(diào)整了參數(shù),卻未收到讓人滿意成效,且生產(chǎn)機(jī)比測試機(jī)配置高,這些問題已經(jīng)是屢見不鮮了,問題原因有很多方面,通常是因為生產(chǎn)實際使用場景與測試機(jī)壓力測試場景不一致,系統(tǒng)受到的壓力不一樣所導(dǎo)致的。
但GC調(diào)整在系統(tǒng)能夠性能調(diào)優(yōu)上確是一個必不可少的環(huán)節(jié),合理的創(chuàng)建使用對象,選擇適合的GC策略,往往能讓性能有一定的提高,也減少了內(nèi)存溢出的可能性,或者縮短系統(tǒng)停頓的時間。
以下是一段發(fā)生在IBM SDK5.0版本上配置了分代并發(fā)策略所產(chǎn)生的GC日志,讓我們一起看下如何分析這一段GC:
<!—距離上次GC間隔時間為6.787 秒
<af> 表示本次垃圾回收是因為分配失敗而引發(fā)的,如果標(biāo)簽開頭寫著sys,則表示應(yīng)用中有顯示調(diào)用GC,如System.gc()。一般情況下不建議顯示調(diào)用GC,當(dāng)然也可以通過配置防止顯示GC, 在JVM啟動參數(shù)中加入-Xdisableexplicitgc參數(shù)屏蔽顯式GC。
Type=“nursery” :這次GC的類型是新生代方式,因為新生代的分配失敗而進(jìn)行垃圾回收了。
Id=”3365”代表GC發(fā)生在新生代,已經(jīng)重復(fù)執(zhí)行了3365次
timestamp:GC的執(zhí)行時間
-->
<af type="nursery" id="3365" timestamp="三 1月 06 16:09:03 2010" intervalms="6787.454">
<!—這里記錄需要申請的堆大小為710824b 約694KB -->
<minimum requested_bytes="710824" />
<!—GC準(zhǔn)備開始前 使用時間為 0.224秒 -->
<time exclusiveaccessms="0.224" />
<!—GC前堆空間使用情況,可以看到剩余567408B,不能滿足空間的申請要求了,申請空間是710824B,因此就需要進(jìn)行GC -->
<nursery freebytes="" totalbytes="42694656" percent="1" />
<!-- GC前的堆空間情況,JVM堆大小為1.2左右了,空閑的空間為650mb左右,大致空閑空間為48%
Tenured:長存(tenured) 區(qū)域
Soa:小對象使用區(qū)域;就是 “正常的” 堆。所有對象最初都分配給小對象區(qū)域,但是如果小區(qū)域已經(jīng)分配完了,則將大于 64KB 的對象分配給大對象區(qū)域。
Loa:大對象使用區(qū)域;大對象區(qū)域是堆中保留給大對象分配的一小片區(qū)域。如果應(yīng)用程序不需要大對象區(qū)域(也就是應(yīng)用程序不分配任何大對象),內(nèi)存管理例程會快速將大對象區(qū)域縮減為空,這樣,整個堆都可以供 “正常的” 分配使用。
-->
<tenured freebytes="666933424" totalbytes="1365881856" percent="48" >
<soa freebytes="654641328" totalbytes="1353589760" percent="48" />
<loa freebytes="12292096" totalbytes="12292096" percent="100" />
</tenured>
<!—GC后堆空間使用情況 -->
<!—詳細(xì)gc情況
type="scavenger":垃圾回收類型為清理過程。
id="3365": 意為執(zhí)行力3365次清理,并且沒有發(fā)生過全局GC,如
<gc type=”global”>。
<flipped objectcount="15558" bytes="3973128" />:說明將要把15558個存活下來的對象被復(fù)制到了幸存區(qū)(survivor space)
<tenured objectcount="14791" bytes="14233400" />:說明將要把14791個對象則經(jīng)過多次幸存區(qū)的復(fù)制,被轉(zhuǎn)移到了長存區(qū)(tenured)
<scavenger tiltratio="65" />:清理的傾斜比率為65%
<refs_cleared soft="73" weak="61" phantom="0" />:清理了存在引用對象的數(shù)目. 弱引用為61、軟引用為73,和虛引用為0. 弱引用、軟引用和虛引用允許靈活的緩存,能夠改進(jìn)應(yīng)用程序的內(nèi)存特性。如果引用對象過多大于1000,GC停頓時間就會受到影響。因此我們需要檢查是否存在過多的應(yīng)用對象。
<finalization objectsqueued="40" />:收尾器,存在需要在GC后,要調(diào)用的自定義方法的對象。
-->
<gc type="scavenger" id="3365" totalid="3709" intervalms="6788.531">
<flipped objectcount="15558" bytes="3973128" />
<tenured objectcount="14791" bytes="14233400" />
<refs_cleared soft="73" weak="61" phantom="0" />
<finalization objectsqueued="40" />
<scavenger tiltratio="65" />
<!—GC后的情況
nursery區(qū)
1、清理了出空閑堆大小為39108112b 約為38191.515625KB,約為37MB
2、 總大小為44144640B,約43110KB 約為42MB
-->
<nursery freebytes="39108112" totalbytes="44144640" percent="88" tenureage="1" />
<!—
Tenured長存區(qū)總大小為1365881856B,約為1.27GB
空閑堆大小為 651143448B 620MB
-->
<tenured freebytes="651143448" totalbytes="1365881856" percent="47" >
<soa freebytes="638851352" totalbytes="1353589760" percent="47" />
<loa freebytes="12292096" totalbytes="12292096" percent="100" />
</tenured>
<time totalms="25.029" />
</gc>
<!--這里再次打印需要申請的堆大小為710824b 約694KB -->
<minimum requested_bytes="710824" />
<!--通過比較,就可以知道當(dāng)前次的GC情況,及堆空間的使用率
<time totalms="26.386" /> 總的GC時間為26.386ms
-->
<nursery freebytes="38397288" totalbytes="44144640" percent="86" />
<tenured freebytes="651143448" totalbytes="1365881856" percent="47" >
<soa freebytes="638851352" totalbytes="1353589760" percent="47" />
<loa freebytes="12292096" totalbytes="12292096" percent="100" />
</tenured>
<time totalms="26.386" />
</af>
<!-- 經(jīng)過垃圾回收,新生代的空間剩余86%,因為分配失敗發(fā)生在新生代,進(jìn)行的是快速的Minor gc,所以長存區(qū)的可用空間依然是47%,這次回收的時間為26.386ms。而
在長存區(qū)發(fā)生的是Major gc,回收時間一般要長于Minor gc,一般健康的JVM 環(huán)境Minor gc:Minor gc=1:10。 -->
打開詳細(xì)垃圾回收,我們就可以獲得詳細(xì)垃圾回收情況,根據(jù)以上的GC日志的分析,我們則可以了解到系統(tǒng)GC提供了什么樣的信息給我們。通過這些信息,我們可以分析得出當(dāng)前GC策略是否需要調(diào)整。(當(dāng)然這個與您實際生產(chǎn)得出的GC日志分析結(jié)果有關(guān))。
在分析的過程里,我們需要關(guān)注幾個點,來分析GC策略是否需要調(diào)整:
l GC頻率:多長時間執(zhí)行一次GC,如果GC頻繁的話,則需要檢查系統(tǒng)現(xiàn)在的運(yùn)行情況是否合理
l GC類型+GC申請空間大小+新生代空間大?。悍治鯣C是否合理
l 各分代區(qū)域的大小:檢查當(dāng)前JVM堆的使用是否合理,新生代的空間大小是否符合當(dāng)前業(yè)務(wù)場景要求。長存區(qū)的空間大小是否合理,再聯(lián)合JVM配置的堆配置大小分析,堆的使用是否存在問題,因為雖然進(jìn)行了GC,但堆空間的使用如不能滿足要求的話,易導(dǎo)致系統(tǒng)出現(xiàn)全局GC,在壓力過大的情況下還會內(nèi)存溢出。
l 重點檢查GC過程中長存區(qū)的變化:是否存在大對象,如果有的話,可以考慮-Xlp參數(shù)優(yōu)化
l GC過程中是否清理了過多引用對象:如有則需要檢查應(yīng)用
l GC整個過程消耗的時間是否過長:過長的GC會直接影響到系統(tǒng)的性能,當(dāng)然這個也GC類型有關(guān),全局GC,清理長存區(qū)的GC時間相對會長點,但是這些類型的GC不會很頻繁。
以上主要是通過詳細(xì)分析GC日志,從而協(xié)助我們進(jìn)一步了解到JVM堆的使用情況,最后得出當(dāng)前GC策略是否符合業(yè)務(wù)應(yīng)用使用場景,而更換其他GC策略則需要配合當(dāng)前業(yè)務(wù)場景與JVM堆使用情況去選擇適合的GC策略。
JDK的版本不同,GC的策略配置也有所不同,但這不是重點,如何讀懂GC日志,分析JVM堆的使用情況后,更根據(jù)問題去尋找適合的JVM選項參數(shù)進(jìn)行設(shè)置,從各方面去調(diào)整最終達(dá)到一個性能調(diào)優(yōu)的目地。
由此可見,垃圾回收的調(diào)整在性能調(diào)優(yōu)過程中也是一個十分重要的環(huán)節(jié)。
其他相關(guān)資料
適合IBM JDK 設(shè)置參數(shù)簡單介紹:
序號
選項
描述
備注
1
-Xlp
此設(shè)置與IBM JVM配合使用,以使用大頁16MB來分配堆
2
Xlp64k
此參數(shù)與IBM JVM配合使用,以使用64K字節(jié)頁大小來分配堆
3
-Xnoclassgc
關(guān)閉類垃圾回收,可以消除由于多次裝入和卸載同一個類而造成地開銷
4
-Xnocompactgc
該參數(shù)完全關(guān)閉壓縮。雖然在性能方面有短期的好處,最終應(yīng)用程序堆將變得支離破碎,即使堆中有足夠的自由空間也會導(dǎo)致 OutOfMemory 錯誤
5
-Xcompactgc
使用該參數(shù)將導(dǎo)致每個垃圾收集周期都執(zhí)行壓縮,無論是否有必要。JVM 在壓縮時要做大量的決策,在普通模式下會推遲壓縮
6
-Xgcthreads
該參數(shù)控制 JVM 在啟動過程中創(chuàng)建的垃圾收集幫助器線程個數(shù)。對于 N-處理器機(jī)器,默認(rèn)的線程數(shù)為 N-1。這些線程提供并行標(biāo)記和并行清理模式中的并行機(jī)制
7
….
….
適合Sun Jvm 輔助配置相關(guān)參數(shù)簡單介紹:
序號
選項
描述
備注
1
-XX:+PrintGC
輸入gc收集概括。輸出形式:
[GC 118250K->113543K(130112K), 0.0094143 secs]
[Full GC 121376K->10414K(130112K), 0.0650971 secs]
2
-XX:+PrintGCDetails
輸出Gc明細(xì)情況,輸出形式:
[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]
[GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]
3
-XX:+PrintGCTimeStamps
輸出GC時間,輸出形式:
11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
4
-Xloggc:filename
指定輸出記錄文件
5
-XX:+PrintGCApplicationConcurrentTime:
打印每次垃圾回收前,程序未中斷的執(zhí)行時間, 輸出形式:Application time: 0.5291524 seconds
6
-XX:+PrintGCApplicationStoppedTime
打印垃圾回收期間程序暫停的時間。可與上面混合使用
輸出形式:Total time for which application threads were stopped: 0.0468229 seconds
7
-XX:+PrintHeapAtGC
打印GC前后的詳細(xì)堆棧信息
8
-verbose:gc
詳細(xì)垃圾收集信息