某台机器的内存比较大,之前的JVM参数是4G的堆,在压测过程中发现当QPS上来以后,Full GC会开始抬头,YoungGC的频率就不用说了,比较高。
观察GC日志和jstat -gcutil,感觉是QPS在峰值的时候对象创建比较多,也有大对象产生。于是打算加大堆的大小来延缓GC的时机,并且有一些GC参数的优化,反复调整后找到了一个适合我们的参数(没有一个best的参数,还是得按照应用的的情况去测量,最好是一遍压测一遍调整,最终找到一个best fit的参数组)。
在把堆调大的过程中比较担心下面几个问题:
1、GC的压力会不会增大?
2、一次GC的耗时会不会增加?
3、如果在GC stop-the-world的时间里,rt飙高怎么办?
这些问题最终都得到了解答,参考CMS GC的原理,结合jstat的观察,有了如下的结论。
新生代扩大3倍后,YoungGC每次会从root开始寻找存活的引用,而增大内存其实并不会导致存活对象增多(死亡对象是会增加的),因为只要你的QPS和rt是稳定的,那么同时存在的线程数也是稳定的,一个线程对应一个request,一个request中生成的对象相对是固定的,也就是说,只要QPS和rt稳定,只要内存足够,调的更大,其实正在处理的请求中的对象还是那么多,一次扫描的时间是不会明显增长的,所以往s0和s1拷贝的对象空间也是不会明显增长的,这导致YoungGC的开销和时间,其实并不会像配置的参数那样成倍增长。
而实际上,通过加大新生代的大小,YoungGC的频率明显减小,因为YoungGC是要stop-the-world的,所以应用停机的时间也会缩短。
旧生代的内存增大,可以避免QPS比较高时,内存迅速占满OldGen,触发Full GC。而对于CMS GC而言,因为上面说的新生代live对象不会明显增长,对remark阶段的耗时也是不会增长太多的,而CMS GC的sweep阶段是并发的,通过jstat可以看到Old区的占用百分比是慢慢减少的,sweep的过程对应用的rt影响不明显。
另外加入了-XX:+CMSScavengeBeforeRemark这个参数,用于在remark之前先做一次YoungGC,目的也是为了减少remark的时间,毕竟remark是要stop-the-world的。