G1参数介绍和调优
堆和新生代大小的计算、分区设置、G1停顿预测模型相关
- 参数G1HeapRegionSize指定堆分区大小。分区大小可以指定,也可以不指定;不指定时,由内存管理器启发式推断分区大小。
- 参数xms/xmx指定堆空间的最小值/最大值。一定要正确设置xms/xmx,否则将使用默认配置,将影响分区大小推断。
- 在以前的内存管理器中(非G1),为了防止新生代因为内存不断地重新分配导致性能变低,通常设置Xmn或者NewRatio。但是G1中不要设置MaxNewSize、NewSize、Xmn和NewRatio。原因有两个,第一G1对内存的管理不是连续的,所以即使重新分配一个堆分区代价也不高,第二也是最重要的,G1的目标满足垃圾收集停顿,这需要G1根据停顿时间动态调整收集的分区,如果设置了固定的分区数,即G1不能调整新生代的大小,那么G1可能不能满足停顿时间的要求。具体情况本书后续还会继续讨论。
- 参数GCTimeRatio指的是GC与应用程序之间的时间占比,默认值为9,表示GC与应用程序时间占比为10%。增大该值将减少GC占用的时间,带来的后果就是动态扩展内存更容易发生;在很多情况下10%已经很大,例如可以将该值设置为19,则表示GC时间不超过5%。
- 根据业务请求变化的情况,设置合适的扩展G1ExpandByPercentOfAvailable速率,保持效率。
- JVM在对新生代内存分配管理时,还有一个参数就是保留内存G1ReservePercent(默认值是10),即在初始化,或者内存扩展/收缩的时候会计算更新有多少个分区是保留的,在新生代分区初始化的时候,在空闲列表中保留一定比例的分区不使用,那么在对象晋升的时候就可以使用了,所以能有效地减小晋升失败的概率。这个值最大不超过50,即最多保留50%的空间,但是保留过多会导致新生代可用空间少,过少可能会增加新生代晋升失败,那将会导致更为复杂的串行回收。
- G1NewSizePercent是一个实验参数,需要使用-XX:+UnlockExperimentalVMOptions才能改变选项。有实验表明G1在回收Eden分区的时候,大概每GB需要100ms,所以可以根据停顿时间,相应地调整。这个值在内存比较大的时候需要减少,例如32G可以设置-XX:G1NewSizePercent=3,这样Eden至少保留大约1GB的空间,从而保证收集效率。
- 参数MaxGCPauseMillis指期望停顿时间,可根据系统配置和业务动态调整。因为G1在垃圾收集的时候一定会收集新生代,所以需要配合新生代大小的设置来确定,如果该值太小,连新生代都不能收集完成,则没有任何意义,每次除了新生代之外只能多收集一个额外老生代分区。
- 参数GCPauseIntervalMillisGC指GC间隔时间,默认值为0,GC启发式推断为MaxGCPauseMillis+1,设置该值必须要大于MaxGCPauseMillis。
- 参数G1ConfidencePercent指GC预测置信度,该值越小说明基于过去历史数据的预测越准确,例如设置为0则表示收集的分区基本和过去的衰减均值相关,无波动,所以可以根据过去的衰减均值直接预测下一次预测的时间。反之该值越大,说明波动越大,越不准确,需要加上衰减方差来补偿。
- JVM中提供了一个对象对齐的值ObjectAlignmentInBytes,默认值为8,需要明白该值对内存使用的影响,这个影响不仅仅是在JVM对对象的分配上面,正如上面看到的它也会影响对象在分配时的标记情况。注意这个值最少要和操作系统支持的位数一致才能提高对象分配的效率。所以32位系统最少是4,64位最少是8。一般不用修改该值。
对象分配相关(快速分配、慢速分配)
- 在优化调试TLAB的时候,在调试环境中可以通过打开PrintTLAB来观察TLAB分配和使用的情况。
- 参数UseTLAB,指是否使用TLAB。大量的实验可以证明使用TLAB能够加速对象分配;该参数默认是打开的,不要关闭它。
- 参数ResizeTLAB,指是否允许TLAB大小动态调整。前面提到TLAB会进行动态化调整,主要是基于历史信息(分配大小、线程数等),有基准测试表明使用动态调整TLAB大小效率更高。
- 参数MinTLABSize,指设置TLAB的最小值。实际应用需要设置该值,比如64K,一般可以根据情况设置和调整该值。
- 参数TLABSize,指设置TLAB的大小。实际中不要设置TLABSize,设置之后TLAB就不能动态调整了,即会使用一个固定大小的TLAB,前面我们提到GC可以根据情况动态调整TLAB,在分配效率和内存碎片之间找到一个平衡点,如果设置该值则这种平衡就失效了。
- 参数TLABWasteTargetPercent,指的是TLAB可占用的Eden空间的百分比,默认值是1。可以根据情况调整TLABWasteTargetPercent,增大则可以分配更多的TLAB,3.1节中给出了具体的计算方式;另外如果实际中线程数目很多,建议增大该值,这样每个线程的TLAB不至于太小。
- 参数TLABRefillWasteFraction,指的是TLAB中浪费空间和TLAB块的比例,默认值是64。可以根据情况调整TLABRefillWasteFraction,主要考量点是内存碎片和分配效率的平衡,如果发现日志waste中的slow和fast很大,说明浪费严重,可以适当减少该参数值。
- 参数TLABWasteIncrement,指的是动态的增加浪费空间的字节数,默认值是4。增加该值会增加TLAB浪费的空间;一般不用设置。
- 参数GCLockerRetryAllocationCount默认值为2,表示当分配中的垃圾回收次数超过这个阈值之后则直接失败。最后再强调一点,TLAB不是G1才引入的,对象分配是JVM提供的基础分配功能,只不过G1结合自己内存分区的特征,以及垃圾回收的具体实现,重新实现了分配的策略,重用了这些参数的功能和使用方法,且没有引入额外的参数,所以这一部分内容不仅适用于G1的调优,其他的垃圾回收器同样适用。
关于Refine线程(处理分区间引用)
- 参数G1ConcRefinementThreads,指的是G1 Refine线程的个数,默认值为0,G1可以启发式推断,将并行的线程数ParallelGCThreads作为并发线程数,其中并行线程数可以设置,也可以启发式推断。通常大家不用设置这个参数,并行线程数可以简单总结为CPU个数的5/8,具体的推断方法见上文。
- 参数G1UpdateBufferSize,指的是DCQ的长度,默认值是256,增大该值可以保存更多的待处理引用关系。
- 参数G1UseAdaptiveConcRefinement,默认值为true,表示可以动态调整Refinement Zone的数字区间,调整的依据在于RSet时间是否满足目标时间。
- 参数G1RSetUpdatingPauseTimePercent,默认值为10,即RSet所用的全部时间不超过GC完成时间的10%。如果超过并且设置了参数G1UseAdaptiveConcRefinement为true,更新Green Zone的方法为:当RSet处理时间超过目标时间,Green zone变成原来的0.9倍,否则如果更新的处理过的队列大于Green Zone,增大Green zone为原来的1.1倍,否则不变;对于YellowZone和Red Zone分别为Green Zone的3倍和6倍。这里特别要注意的是当动态变化时,可能导致Green Zone为0,那么Yellow Zone和Red Zone都为0,如果这种情况发生,意味着Refine线程不再工作,利用Mutator来处理RSet,这通常绝非我们想要的结果。所以在设置的时候,可以关闭动态调整,或者设置合理的RSet处理时间。关闭动态调整需要有更好的经验,所以设置合理的RSet处理时间更为常见
- 参数G1ConcRefinementThresholdStep,默认值为0,如果没有定义G1会启发式推断,依赖于Yellow Zone和Green Zone。这个值表示的是多个更新RSet的Refine线程对于整个DirtyCardQueueSet的处理步长。
- 参数G1ConcRefinementServiceIntervalMillis,默认值为300,表示RS对新生代的抽样线程间隔时间为300ms。
- 参数G1ConcRefinementGreenZone,指定Green Zone的大小,默认值为0,G1可以启发式推断。如果设置为0,那么当动态调整关闭,将导致Refine工作线程不工作,如果不进行动态调整,意味着GC会处理所有的队列;如果该值不为0,表示Refine线程在每次工作时会留下这些区域,不处理这些RSet。这个值如果需要设置生效的话,要把动态调整关闭。通常并不设置这个参数。
- 参数G1ConcRefinementYellowZone,指定Yellow Zone的大小,默认值为0,G1可以启发式推断,是Green Zone的3倍。
- 参数G1ConcRefinementRedZone,指定Red Zone的大小,默认值为0,G1可以启发式推断,是Green Zone的6倍,通常来说并不需要调整G1ConcRefinementGreenZone、G1ConcRefinementYellowZone和G1ConcRefinementRedZone这3个参数,但是如果遇到RSet处理太慢的情况,也可以关闭G1UseAdaptiveConcRefinement,然后根据Refine线程数目设置合理的值。
- 参数G1ConcRSLogCacheSize,默认值为10,即存储hot card最多为210,也就是1024个。那么超过1024个该如何处理?实际上JVM设计得很简单,超过1024,直接把老的那个card拿出去处理,相当于认为它不再是hot card。
- 参数G1ConcRSHotCardLimit,默认值为4,当一个card被修改4次,则认为是hot card,设计hot card的目的是为了减少该对象修改的次数,因为RSet在被引用的分区存储,所以可能有多个对象引用这个对象,再处理这个对象的时候,可以一次性地把这多个对象都作为根。
- 参数G1RSetRegionEntries,默认值为0,G1可以启发式推断。base*(log(region_size/1M)+1),base的默认值是256,base仅允许在开发版本设置,在发布版本不能更改base。这个值很关键,太小将会导致RSet的粒度从细变粗,导致追踪标记对象将花费更多的时间。另外,从上面的公式中也可以得到:通过调整HeapRegionSize来影响该值的推断,如人工设置HeapRegionSize。实际工作中也可以根据业务情况直接设置该值(如设置为1024);这样能保持较高的性能,此时每个分区中的细粒度卡表都使用1024项,所有分区中这一部分占用的额外空间加起来就是个不小的数字了,这也是为什么RSet浪费空间的地方。
- 参数G1SummarizeRSetStats打印RSet的统计信息,G1SummarizeRSetStatsPeriod=n,表示GC每发生n次就统计一次,默认值是0,表示不会周期性地收集信息。在生产中通常不会使用信息收集。