最近线上服务运行比较缓慢,老大提出让我进行JVM优化。GC的内容很多,也不可能一时间全部都掌握,今天就要看看G1的一些知识,还有调优时可调整的参数。
1.G1简介
G1的全称为 Garbage First Garbage Collector, 是一款内置在HotSpot JVM中的服务端垃圾收集器。
G1是作为CMS的替代产品出现的,其目标是在满足最短时间停顿的同时达到一个高吞吐量,适用于多核处理器,大内存容量的系统。
其特点如下:
- 短停顿时间且可控。对内存进行分区,可以应用在大内存系统中,其使用了基于内存的新生代收集和混合收集。
- 高吞吐量。设计了新的并发标记线程,并发处理分区之间的引用关系,加快垃圾回收速度。
1.1分区
G1垃圾回收器将堆内存划分成固定大小的Region,下图为G1内存的分配示意图,其中灰色格子代表一个region。
其中G1的分区可以分为4类:
- 自由分区(Free Heap Region, FHR)
- 新生代分区(Young Heap Region , YHR) 新生代也分为Eden 和Survivor区
- 大对象分区(Humongous Heap Region,HHR) 大对象区可分为 大对象头分区和大对象连续分区,大对象一般占Region的一半以上。
- 老生代分区(Old Heap Region,OHR)
img
Region可以在1MB~32MB,且为2的N次幂,设定分区大小有以下方法:
- 可以通过-XX:G1HeapRegionSize=<>来指定大小,默认为0.
- 默认情况下是将整个堆分为2048个Region。
1.1.1 新生代大小
新生代大小的设置如下:
- 如果设置了最大值 (MaxNewSize) 和最小值(NewSize), Xmn 等价于MaxNewSize
- 如果设置了最大值和最小值,又设置了NewRatio 则忽略NewRatio
- 如果没有设置新生代最大值和最小值,但是设置了NewRatio 则新生代的最大值最小值是相同的,都是整个堆空间的 (NewRatio+1)
- 如果没有设置新生代最大值和最小值,或者只设置了最大值或者最小值中的一个,那么G1将根据参数G1MaxNewSizePrecent(默认值60) 和 G1NewSizePercent (默认值5)占整个堆空间的比例计算最大值和最小值。
关于堆大小的参数优化:
- G1HeapRegionSize 可以指定堆的大小,可指定也可以有内存管理启发推断分区大小。
- xms/xmx 指定堆空间的最小值/最大值, 一定要设置正确的值,否则会影响分区大小推断。
- G1不要设置MaxNewSize,NewSize,Xmn, NewRatio,即不要显示的设置年轻代的大小 。G1对内存的管理不是连续的,所以即使重新分配一个堆分区的代价不大;G1的目标满足垃圾收集停顿,这需要G1根据停顿时间动态调整收集的分区,如果设置了固定的分区数,G1不能调整新生代的大小,则不容易满足停顿时间的需求。
- GCTimeRatio指的是GC与应用程序之间的时间占比,默认值是9,表示GC与程序的时间占比为90%,增大该值将减少GC占用时间,增大该值则动态扩展内存会更容易发生。
2.G1 GC可优化参数
G1提供了两种GC模式,Young GC 和 Mixed GC 两种GC都会有STW.
Young GC
主要是对Eden区进行GC ,一般情况下,会在Eden Region使用达到最大阈值时,空间内存不够用时,触发YoungGC。每次Young GC会回收所有的Eden 和Serviour区,并且将存活对象复制到Old区以及一些Survivor区。
Mixed GC
Mixed GC 会选取(并发标记)所有的 Young Region和 回收收益较高的一些 Old Region, 然后进行年轻代回收算法。
混合回收分为两个阶段。
其中并发标记阶段可以分为以下几个子阶段:
- 初始标记子阶段:标记所有直接可达的根对象,此阶段会STW,
- 并发标记子阶段:YoungGC 执行完成之后,如果满足并发标记的的条件(已分配及将要分配的内存占总内存的比例超过阈值之后),就进行并发标记,其中-XX:ConcGCThreads 控制并发标记线程数量,一个线程每次扫描一个Region。此时标记存活对象,
- 再标记子阶段:找出所有未被访问的存活对象,此过程为并发执行,并且会有STW,其中-XX:ParallelGCThreads可指定GC暂停时可用的GC线程数。
- 清理子阶段:需要STW,存活对象计数,整理标记位图,释放完全空闲的分区。
混合回收阶段的参数优化:
- 参数InitiatingHeapOccupancyPercent(IHOP),默认值时45, 此值时启动并发标记的先决条件,只有已分配内存占总空间超过45%之后,才会启动并发标记任务。增加此值,将导致并发标记可能花费更多的时间,也会让YGC或者MixedGC时收集的分区变少,这样就会导致更多的Full GC。这个值可以根据整体应用占用的平均内存来设置,可以把该值设置的比平均内存稍微高一点。IHOP的设置效果很明显,但是要设置合理的值并不容易,需要更多的性能测试来判断。
- 参数G1ReservePercent, 默认值是10,如果GC晋升失败导致FullGC,则可以调大该值
- 参数ConcGCThreads为并发线程数,默认值为0,如果未设置,可以动态调整,并且使用ParallelGCThreads为依据来推断,如果并发标记耗时较大,可以增大并发线程数。
- HeapSizePerGCThread 默认为64M,表示每64M分配一个线程
- 参数UseDynamicNumberOfGCThreads,默认为false,设为true表示可以动态调整线程数,调整范围会根据最大线程数,HeapSizePerGCThread确定。
- 参数GCDrainStackTargetSize,默认值为64,表示并发标记子阶段,一次标记最多标记的最多对象个数。
- 参数GCMixedGCLiveThresholdPercent 默认值85,用于判断分区能否被加入到CSet中,低于该值将会被加入。
- 参数G1HeapWastePercent 默认值5,即当Cset中可回收空间 占总空间的比例大于G1HeapWastePercent才会开始混合回收。
- 参数G1MixedGCCountTarget,默认值为8,这个参数越大,收集老年代的分区越少,反之收集的分区就越多,尽量保持老年代分区在Cset中的比例超过1/G1MixedGCCountTarget。
- 参数G1OldCSetRegionThresholdPercent 默认值为10,表示最多收集10%的分区。
- 参数G1ConcMarkStepDurationMillis 默认值为10,表示每个并发标记子阶段最多执行10ms
FullGC发生之后,基本都是串行回收. 如果不幸发生了FullGC, 那么我们能做的就是尽量让FullGC尽快完成,然后降低其频率。但是通常情况下,比较固定且较长时间间隔的FullGC是被允许的。
那么FullGC相关也是有一些优化调整的地方:
- 使用参数MinHeapFreeRatio 用于判断是否可以扩展堆空间,增大该值扩展的概率就会变小。
- MaxHeapFreeRatio 判断是否可以收缩空间,增大该值收缩的概率也会变小。
- MarkSweepAlwaysCompactCount 默认值为4,这个值表示经过一定次数的GC之后,允许当前区域中一定比例的死亡对象当作存活对象处理,暂时不回收,从而加快FullGC的处理流程。这个比例可以使用MarkSweepDeadRatio来修改,默认值为5.
总结
以上是一些优化参数的使用,至于具体调优的目的要根据我们各个程序的要求。一般而言需要满足最大的吞吐量和最小的暂停时间,GC频率尽量低,堆空间的有效利用率高等。可调整的部分有内存参数的优化,引用的处理(Rset),并发标记(Mark),垃圾回收部分。
Oracle官方有一些推荐调优的方向:
- 针对年轻代的设置,尽量避免明确的设置年轻代的大小(使用-Xmn,-XX:NewRatio等),固定的年轻代大小会覆盖最小停顿时间的目标。
- 对于暂停时间的目标,我们需要考虑平衡延迟和吞吐量,两者不可兼得,所以需要找到一个最佳的平衡点。
- 混合回收阶段的优化参数可以考虑率使用 -XX:InitiatingHeapOccupancyPercent 修改内存占用比(具体可以参考前文), -XX:G1MixedGCLiveThresholdPercent 和 -XX:G1HeapWastePercent 改变混合垃圾回收的策略,-XX:G1MixedGCCountTarget 和 -XX:G1OldCSetRegionThresholdPercent 调整老年代在CSet中的占比