JVM GC調整優化過程全揭秘
JVM GC調整優化是以個極為復雜的過程,由于各個程序具備不同的特點,如:web和GUI程序就有很大區別(Web可以適當的停頓,但GUI停頓是客戶無法接受的),而且由于跑在各個機器上的配置不同(主要cup個數,內存不同),所以使用的JVM GC種類也會不同。接下來,我簡單介紹一下如何進行JVM GC調整優化。
首先說一下如何監視JVM GC,你可以使用我以前文章中提到的JDK中的jstat工具,也可以在java程序啟動的opt里加上如下幾個參數(注:這兩個參數只針對SUN的HotSpotVM):
- -XX:-PrintGCPrintmessagesatgarbagecollection.Manageable.
- -XX:-PrintGCDetailsPrintmoredetailsatgarbagecollection.Manageable.(Introducedin1.4.0.)
- -XX:-PrintGCTimeStampsPrinttimestampsatgarbagecollection.Manageable(Introducedin1.4.0.)
當把-XX:-PrintGCDetails加入到javaopt里以后可以看見如下輸出:
[GC[DefNew:34538K->2311K(36352K),0.0232439secs]45898K->15874K(520320K),0.0233874secs]
[FullGC[Tenured:13563K->15402K(483968K),0.2368177secs]21163K->15402K(520320K),[Perm:28671K->28635K(28672K)],0.2371537secs]
他們分別顯示了JVM GC的過程,清理出了多少空間。第一行GC使用的是‘普通GC’(MinorCollections),第二行使用的是‘全GC’(MajorCollections)。他們的區別很大,在第一行最后我們可以看見他的時間是0.0233874秒,而第二行的FullGC的時間是0.2371537秒。第二行的時間是第一行的接近10倍,也就是我們這次調優的重點,減少FullGC的次數,以為FullGC會暫停程序比較長的時間,如果FullGC的次數比較多。程序就會經常性的假死。當然這只是他們的表面現象,接下來我仔細介紹一下GC,和FullGC(為后面的調優做準備)。
我們知道Java和C++的區別主要是,Java不需要像c++那樣,由程序員主動的釋放內存。而是由JVM里的GC(GarbageCollection)來,在適當的時候替我們釋放內存。JVM GC調整優化的內部工作,即JVM GC的算法有很多種,如:標記清除收集器,壓縮收集器,分代收集器等等。現在比較常用的是分代收集(也是SUNVM使用的),即將內存分為幾個區域,將不同生命周期的對象放在不同區域里(新的對象會先生成在Youngarea,在幾次GC以后,如過沒有收集到,就會逐漸升級到Tenuredarea)。在JVM GC收集的時候,頻繁收集生命周期短的區域(Youngarea),因為這個區域內的對象生命周期比較短,GC效率也會比較高。而比較少的收集生命周期比較長的區域(OldareaorTenuredarea),以及基本不收集的永久區(Permarea)。
注:Youngarea又分為三個區域分別叫Eden,和倆個Survivorspaces。Eden用來存放新的對象,Survivorspaces用于新對象升級到Tenuredarea時的拷貝。
我們管收集生命周期短的區域(Youngarea)的收集叫GC,而管收集生命周期比較長的區域(OldareaorTenuredarea)的收集叫FullGC,因為他們的收集算法不同,所以使用的時間也會不同。我們要盡量減少FullGC的次數。
接下來介紹一下HotSpotVMGC的種類,GC在HotSpotVM5.0里有四種。一種是默認的叫serialcollector,另外幾種分別叫throughputcollector,concurrentlowpausecollector,incremental(sometimescalledtrain)lowpausecollector(廢棄掉了)。以下是SUN的官方說明:
1.Thethroughputcollector:thiscollectorusesaparallelversionoftheyounggenerationcollector.Itisusedifthe-XX:+UseParallelGCoptionispassedonthecommandline.Thetenuredgenerationcollectoristhesameastheserialcollector.
2.Theconcurrentlowpausecollector:thiscollectorisusedifthe-Xincgc™or-XX:+UseConcMarkSweepGCispassedonthecommandline.Theconcurrentcollectorisusedtocollectthetenuredgenerationanddoesmostofthecollectionconcurrentlywiththeexecutionoftheapplication.Theapplicationispausedforshortperiodsduringthecollection.Aparallelversionoftheyounggenerationcopyingcollectorisusedwiththeconcurrentcollector.Theconcurrentlowpausecollectorisusediftheoption-XX:+UseConcMarkSweepGCispassedonthecommandline.
3.Theincremental(sometimescalledtrain)lowpausecollector:thiscollectorisusedonlyif-XX:+UseTrainGCispassedonthecommandline.ThiscollectorhasnotchangedsincetheJ2SEPlatformversion1.4.2andiscurrentlynotunderactivedevelopment.Itwillnotbesupportedinfuturereleases.Pleaseseethe1.4.2GCTuningDocumentforinformationonthiscollector.
簡單來說就是throughputcollector和concurrentlowpausecollector:使用多線程的方式,利用多CUP來提高GC的效率,而throughputcollector與concurrentlowpausecollector的去別是throughputcollector只在youngarea使用使用多線程,而concurrentlowpausecollector則在tenuredgeneration也使用多線程。
根據官方文檔,他們倆個需要在多CPU的情況下,才能發揮作用。在一個CPU的情況下,會不如默認的serialcollector,因為線程管理需要耗費CPU資源。而在兩個CPU的情況下,也挺高不大。只是在更多CPU的情況下,才會有所提高。當然concurrentlowpausecollector有一種模式可以在CPU較少的機器上,提供盡可能少的停頓的模式,見下文。
當要使用throughputcollector時,在javaopt里加上-XX:+UseParallelGC,啟動throughputcollector收集。也可加上-XX:ParallelGCThreads=
當要使用concurrentlowpausecollector時,在java的opt里加上-XX:+UseConcMarkSweepGC。concurrentlowpausecollector還有一種為CPU少的機器準備的模式,叫Incrementalmode。這種模式使用一個CPU來在程序運行的過程中GC,只用很少的時間暫停程序,檢查對象存活。
在Incrementalmode里,每個收集過程中,會暫停兩次,第二次略長。第一次用來,簡單從root查詢存活對象。第二次用來,詳細檢查存活對象。整個過程如下:
- *stopallapplicationthreads;dotheinitialmark;resumeallapplicationthreads(第一次暫停,初始話標記)
- *dotheconcurrentmark(usesoneprocesorfortheconcurrentwork)(運行是標記)
- *dotheconcurrentpre-clean(usesoneprocessorfortheconcurrentwork)(準備清理)
- *stopallapplicationthreads;dotheremark;resumeallapplicationthreads(第二次暫停,標記,檢查)
- *dotheconcurrentsweep(usesoneprocessorfortheconcurrentwork)(運行過程中清理)
- *dotheconcurrentreset(usesoneprocessorfortheconcurrentwork)(復原)
當要使用Incrementalmode時,需要使用以下幾個變量:
- -XX:+CMSIncrementalModedefault:disabled啟動i-CMS模式(mustwith-
- XX:+UseConcMarkSweepGC)
- -XX:+CMSIncrementalPacingdefault:disabled提供自動校正功能
- -XX:CMSIncrementalDutyCycle=default:50啟動CMS的上線
- -XX:CMSIncrementalDutyCycleMin=default:10啟動CMS的下線
- -XX:CMSIncrementalSafetyFactor=default:10用來計算循環次數
- -XX:CMSIncrementalOffset=default:0最小循環次數(Thisisthepercentage(0-
- 100)bywhichtheincrementalmodedutycycleisshiftedtotherightwithintheperiod
- betweenminorcollections.)
- -XX:CMSExpAvgFactor=default:25提供一個指導收集數
SUN推薦的使用參數是:
- -XX:+UseConcMarkSweepGC\
- -XX:+CMSIncrementalMode\
- -XX:+CMSIncrementalPacing\
- -XX:CMSIncrementalDutyCycleMin=0\
- -XX:CMSIncrementalDutyCycle=10\
- -XX:+PrintGCDetails\
- -XX:+PrintGCTimeStamps\
- -XX:-TraceClassUnloading
注:如果JVM GC中使用throughputcollector和concurrentlowpausecollector,這兩種垃圾收集器,需要適當的挺高內存大小,以為多線程做準備。JVM GC調整優化到此結束。
【編輯推薦】