Gc调优的目标:1.降低停顿时间 2.提高吞吐量 3.避免full-gc

调优可以使用的手段:
1.各个内存区的大小调整:堆,年轻代,老年代,方法区等等
2.减少短暂对象的存活时间,提高长期对象的复用率(对象池,cache,永久区等等),减少不必要的垃圾的生产。
3.调整对象的晋升:晋升阈值,survivor的目标存活值,大小等
3.并发控制:并发,并行的gc线程数
3.提前开始并发周期
4.G1可以调整mix-清理周期的目标值,尽快结束mix-清理周期。

1. 降低停顿时间+保存合理的吞吐量:
年轻代:调小年轻代的大小+减少垃圾的产生速度+减少垃圾的存活时间。即,没必要产生的对象就不用创建了,一次性的对象要让它死的快,不容易死的对象要增加复用率。这还有利于减少对象的晋升,减少老年代的压力。
老年代:a.同样可以调小老年代的大小。但是也会带来吞吐量的问题。如果物理内存够大的话,可以使用G1。同样可以减少对象的存活时间,这样可以在老年代的gc中,尽快的清理掉多数的垃圾,可以防止晋升失败和并发失败。
b.加快老年代的gc速度。可以调整并发值,也就是gc的线程数。但是要考虑机器的cpu资源和应用的cpu使用情况,要尽量的提高cpu的使用率,但是不要造成抢占CPU,因为这样会使吞吐量下降。
2. 提高吞吐量:
1. 在gc停顿和频率直接的权衡
2. 线程和cpu资源的考量
3. Gc算法 并行 vs 并发
3. 避免full-gc:
Full-gc的导致有几个原因:1.超大对象 2.并发失败 3.内存碎片 4.永久代满了
并发失败的造成原因是对象的晋升速度超过了老年代的清理速度。所以解决方案就是让这场追赶比赛的悬殊更大一些。
1. 增加堆的大小
2. 降低年轻代的垃圾晋升速度: 对象的快速死亡,减少不必要的对象创建,survivor区调整(目标:充分利用survivor区,保证对象是到达一定年龄才发生晋升而不是 因为溢出而晋升),调大堆区
3. 加快并发周期的速度:并发控制
4. 提前并发周期:提前并发周期(这会导致并发周期的频率增加,所以需要进行权衡),G1调整清理周期目标(清理周期太长,会导致并发周期时间太长,如果清理效率一般,会导致并发失败。清理周期太短,可能会导致并发周期太频繁,清理不够彻底,也有可能导致并发失败,所以也需要进行权衡)
5. 提高老年代收集效率:减少对象生命周期
4. Remark阶段的耗时优化:
“-XX:+PrintReferenceGC”参数,细化GC日志中对系统内的软引用、弱引用、虚引用的回收耗时。加上后发现,发现回收这些引用的耗时+类卸载的耗时成为了整个CMS reamrk阶段的瓶颈。由于JDK8在Linux环境会默认开启CMSClassUnloadingEnabled,这会使得CMS在CMS-remark阶段尝试进行类的卸载,然而通过cat可以得知Metaspace空间稳定,无需每次fullgc都去主动卸载,故需要显示关闭CMSClassUnloadingEnabled

survivor区的调优:
目标: 1. 最基本的目标是保证对象是因为达到晋升阈值才到达老年代,而不是因为溢出而到达老年代 2. 在目标1的基础上,尽可能的加大对象的晋升阈值,这样可以减轻老年代的压力,当然也要考虑这样给年轻代造成的压力。最好不要为了这个目标而把老年代调小,这样会使full-gc的风险加大
方法:1. 晋升阈值的设置:-XX:MaxTenuringThreshold(最大晋升阈值) -XX:InitialTenuringThreshold=N (初始晋升阈值) ,有时为了避免溢出导致的晋升,不得已把晋升阈值调小
2. 设置survivor区的目标利用率(YGC后,survivor的占用率) -XX:TargetSurvivorRatio 默认是50,一般这也是合适的
3. 减少对象的存活时间
4.调整堆的大小
思路:
1. -XX:+PrintTenuringDistribution 使用这个参数,gc日志会打印各个年龄的对象的大小,再通过survivor的大小,我们就可以推断是否会发生溢出了,也可以根据survivor的大小设置合适的晋升阈值和目标值
2. 不可把survivor增加过大,因为会对象是直接在eden上分配的,如果eden减少,会带来更多的性能问题。也不可过多减少老年代的大小,这样会导致full-gc风险加大。最好的方法是增加堆的大小,调小survivor的利于率目标值。这样eden和老年代都大概维持原有的大小,survivor区会加大。

TLAB: TLAB位于Eden区,是线程的进行快速分配内存是区域。因为它是thread local的,不像TLAB外的区域需要进行同步操作,所以比较快。当一个线程的tlab用尽的时候,有俩种动作:1. 直接在tlab外进行分配对象,这个线程后续的分配还会尝试往这个tlab上进行分配。 2. 放弃这个tlab,在eden区重新分配一块新的tlab。老的tlab会随着ygc进行垃圾回收,但是老的tlab上的剩余空间会被浪费。 操作这俩个动作的选择是-XX:TLABWasteTargetPercent=N -XX:TLABWasteIncrement=N -XX:TLABSize=N
这些参数。它的大小默认是动态计算的。每次在tlab外分配完成后,都会使TLABWasteTargetPercent的值变大。
Tlab的大小默认由三个因素控制:应用程序的线程数、Eden 空间的大小以及线程的分配率 使用 -XX:-UseTLAB 可以关闭tlab


大对象: 当一个大对象无法在年轻代放得下时,就回直接进入老年代。 在G1中,如果大对象大于一个分区的大小,就会被放到老年代,而且在老年代中需要连续的分区来进行存放。如果没有连续的分区,就会触发并发周期,如果还不行,就会进行full-gc。所以大对象的影响是很大的。

 

 


G1调优:
G1 垃圾收集器调优的主要目标是避免发生并发模式失败或者疏散失败
• 通过增加总的堆空间大小或者调整老年代、新生代之间的比例来增加老年代空间的 大小。
• 增加后台线程的数目(假设我们有足够的CPU资源运行这些线程)。
• 以更高的频率进行G1的后台垃圾收集活动。
• 在混合式垃圾回收周期中完成更多的垃圾收集工作。
调优方法:
1. 使用 -XX:MaxGCPauseMillis=N 但是如果过小的话,会导致年轻代和老年代都变小,从而导致gc频繁,full-gc风险变大。
2. 增加后台并发数 ParallelGCThreads ConcGCThreads
3. 调整并发周期频率,尽早开始清理:-XX:InitiatingHeapOccupancyPercent=N G1是根据整个堆区,CMS是根据老年代的大小占用 太大会导致full-gc风险变高,太小会导致gc太频繁
4. 调整混合清理周期:并发周期之后、老年代的标记分区回收完成之前,G1 收集器无法启动新的并发周期。因此,让 G1 收集器更早启动标记周期的另一个方法是在混合式垃圾回收周期中尽量处理更 多的分区(如此一来最终的混合式 GC 周期就变少了,但是单次清理周期的停顿时间呼会变长):
混合式垃圾收集要处理的工作量取决于三个因素。
第一个因素是有多少分区被发现大部分 是垃圾对象。目前没有标志能够直接调节这个因素:混合式垃圾收集中,如果分区的垃圾 占用比达到 35%,这个分区就被标记为可以进行垃圾回收。(这个因素在将来的某个时刻 可能也能调整,在开源的实验版本中已经有名为 -XX:G1MixedGCLiveThresholdPercent=N 的 参数可以对其进行调整)。 第二个因素是 G1 垃圾收集回收分区时的最大混合式 GC 周期数,通过参数 -XX:G1MixedGCCountTarget=N 可以进行调节。这个参数的默认值为 8;减少该参数值可以帮 助解决晋升失败的问题(代价是混合式 GC 周期的停顿时间会更长)。 另一方面,如果混合式 GC 的停顿时间过长,可以增大这个参数的值,减少每次混合式 GC 周期的工作量。不过调整之前我们需要确保增大值之后不会对下一次 G1 并发周期带来 太大的延迟,否则可能会导致并发模式失败。 最后,第三个影响因素是 GC 停顿可忍受的最大时长(通过 MaxGCPauseMillis 参数设定)。 MaxGCPauseMillis 标志设定的混合式周期时长是向上规整的,如果实际停顿时间在停顿最大时长以内,G1 收集器能够收集超过八分之一标记的老年代分区(或者其他设定的值)。 增大 MaxGCPauseMillis 能在每次混合式 GC 中收集更多的老年代分区,而这反过来又能帮 助 G1 收集器在更早的时候启动并发周期。

 


CMS调优:
CMS和G1的不同是,它的清理算法是标记清理,所以最后的清理是并发的,不会造成stw,所以不需要考虑g1中混合清理阶段的一些问题。
CMS的调优目标也是避免并发失败:
做法:
1.想办法增大老年代空间,要么只移动部分的新生代对象到老年代,要么增加更多的堆 空间。 2.以更高的频率运行后台回收线程。 -XX:CMSInitiatingOccupancyFraction=N 和 -XX:+UseCMSInitiatingOccupancyOnly CMSInitiatingOccupancyFraction值的确定,可以先找到一个并发失败的日志,然后查看这次并发周期开始时,老年代的占用比。 XX:+UseCMSInitiatingOccupancyOnly如果不指定, 只是用设定的回收阈值CMSInitiatingOccupancyFraction,则JVM仅在第一次使用设定值,后续则自动调整会导致上面的那个参数不起作用。
3.使用更多的后台回收线程。