简书 许乐
转载请注明原创出处,谢谢!
【案例】
最近我的同学遇到一个线上问题,线上机器的jvm进程频繁FullGC(大约每10分钟一次),登陆机器发现jvm参数只配置了最大堆内存,其他配置都是系统默认配置,问我如何排查并优化?
【特征】
经过排查得知,该机器上部署的业务会产生大量的JSON对象,并且每个JSON对象大小大约在1M~10M之间,但是JSON对象的作用域很短。
在JVM启动参数中,可以设置内存、垃圾回收相关的一些参数设置,默认情况不做任何设置JVM会工作的很好,但对一些配置很高的Server和具体的应用必须仔细调优才能获得最佳性能。
本次事故的原因是由于使用了JVM默认配置:新生代和老年代比例为1:2,将新生代和老年代比例调整为1:1,FullGC 频率明显降低了,业务停顿时间大大缩短。
【分析】
对象的动态年龄判断:
为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄
-----《深入理解Java虚拟机》
由于存在大量的新生对象,而新生代设置相对较小,根据对象的动态年龄判断,对象在新生代提前晋升到老年代,使得老年代迅速增大,又由于老年代的GC算法采用标记清除算法,会产生大量内存碎片,使得老年代剩余空间很大但是没有足够大的连续空间分配给当前对象,而不得不提前触发一次FullGC。
【总结】
JVM参数调优的目的:
- GC的时间足够的小
- GC的次数足够的少
- 发生Full GC的周期足够的长
前两个目的是相悖的,要想GC时间短必须要一个更小的堆,而要保证GC次数足够少,必须保证一个更大的堆,我们只能取其平衡。
(1)针对JVM堆的设置,一般可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相同的值
(2)新生代和老年代将根据默认的比例(1:2)分配堆内存,可以通过调整二者之间的比率NewRadio来调整二者之间的大小,也可以针对回收代,比如新生代,通过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止新生代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小
(3)新生代和老年代设置多大才算合理?这个我问题毫无疑问是没有答案的,否则也就不会有调优。我们观察一下二者大小变化有哪些影响
- 更大的新生代必然导致更小的老年代,大的新生代会延长Minor GC的周期,但会增加每次GC的时间;小的年老年代会导致更频繁的Full GC
- 更小的新生代必然导致更大老年代,小的新生代会导致Minor GC很频繁,但每次的GC时间会更短;大的老年代会减少Full GC的频率
- 如何选择应该依赖应用程序对象生命周期的分布情况:如果应用存在大量的临时对象,应该选择更大的新生代;如果存在相对较多的持久对象,老年代应该适当增大。但很多应用都没有这样明显的特性,在抉择时应该根据以下两点:(A)本着Full GC尽量少的原则,让老年代尽量缓存常用对象,JVM的默认比例1:2也是这个道理 (B)通过观察应用一段时间,看其在峰值时老年代会占多少内存,在不影响Full GC的前提下,根据实际情况加大新生代,比如可以把比例控制在1:1。但应该给老年代至少预留1/3的增长空间。
网友评论