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调整优化到此结束。
【编辑推荐】