JVM系统优化实践(8):订单系统的垃圾回收案例

时间:2022-12-12 00:41:44

您好,我是湘王,这是我的51CTO博客,欢迎您来,欢迎您再来~




上回说到了年轻代和老年代的两个垃圾回收器:ParNew和CMS,并且将CMS的GC过程也一并介绍了,现在来看个订单系统的案例。

假设有这样的订单系统:

1、每日亿级流量,500万DAU,每日50万单集中在4小时高峰期;

2、大促(如双11)期间,就可能产生1000单/秒的峰值;

3、假设需增加3台机器,平均300单/台,4C8G。

案例目标:优化JVM,尽量避免Full GC。

按500单/秒的请求来估算的话,也就是:

500 * 1K * 20倍(单个开销) * 10倍(相关对象) = 100M

即每秒会产生100M(垃圾)。

JVM系统优化实践(8):订单系统的垃圾回收案例


按此消费水平,内存模型是:

1、4C8G内存,JVM内存 = 物理内存 / 2 = 4G内存;

2、JVM堆分配3G(年轻代1.5G,老年代1.5G);

3、JVM栈每线程分配1M;

4、剩余内存再配置其他JVM参数

所以常规的JVM参数配置就是这样的:

-Xms3072M -Xmx3072M -Xmn1536M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M

但按此配置,年轻代15秒后就会被填满。

JVM系统优化实践(8):订单系统的垃圾回收案例


所以稍稍调整下,增加-XX:SurvivorRatio参数:

-Xms3072M -Xmx3072M -Xmn1536M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:SurvivorRatio=8


-XX:SurvivorRatio=8,导致Eden只有1.2G,会提前触发Minor GC:

JVM系统优化实践(8):订单系统的垃圾回收案例



接下来可以再考虑优化Survivor。它的问题是:

1、会经常出现Survivor空间不足而直接进入老年代;

2、超过Survivor空间50%规则(会直接进入老年代)。

建议:

1、调整年轻代和老年代空间大小;

2、因为普通业务系统的大部分对象生成周期都很短,根本不应该频繁进入老年代;

3、尽量让对象留在年轻代里。

因此,对于任何系统,首要考虑的就是尽量让对象都留在Survivor里。调整后的JVM配置:

-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:SurvivorRatio=8


同时,可以降低进入老年代的“年龄”大小,给Survivor腾空间。调整后的JVM配置:

-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5


另一方面,为了不让过大的对象进入老年代,可以考虑对进入老年代的对象大小进行调整,当有超过指定大小的内存对象时,就直接进入老年代。调整后的JVM配置:

-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M


之前说过默认超过Survivor空间大小的50%时,存活对象就会进入老年代,因此这里也可以优化。调整后的JVM配置:

-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:TargetSurvivorRatio=50


最后别忘了指定GC,年轻代用ParNew,老年代用CMS。调整后的JVM配置:

-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:TargetSurvivorRatio=50 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC


那么各位可以根据这个思路,估算一下自己公司的生产系统,合理设置并优化JVM参数。


总结一下:

1、对象进入老年代的一般规律:

l -XX:MaxTenuringThreshold=N,该参数值让年轻代对象躲过N次GC之后进入老年代。这些对象一般是@Entity、@Component、@Service、@Controller等注解标注出来的系统组件;

l -XX:PretenureSizeThreshold=1M,年轻代中超过该值的对象就进入老年代;

l -XX:TargetSurvivorRatio=50,Minor GC后存活对象的总大小如果超过该值就进老年代。

2、什么时候会触发Full GC?

l -XX:HandlePromotionFailure,该参数没打开时触发Full GC(JDK1.6后已废弃);

l 老年代可用空间 < 历次Minor GC后进入老年代的对象的平均大小时触发Full GC;

l 老年代可用空间 < 要进入的老年代的存活对象大小时触发Full GC;

l -XX:+UseCMSCompactAtFullCollection和XX:CMSInitiatingOccupancyFraction=92,老年代可用空间超过该值时触发Full GC,这两个必须配对出现(JDK1.8已不建议使用)。

大促期间,真正的系统压力峰值时间是有限的,比如持续2小时。如果JVM能做到500单/秒,大约1小时才触发一次Full GC,那么峰值过后,JVM的压力就会小很多,就不会触发Full GC了。

3、老年代GC时发生Concurrent Mode Failure的问题。

l 当老年代使用空间超过92%时进行CMS,可用空间不足100M;

l 有一批存活对象要进入老年代,大小200M;

l 此时就发生Concurrent Mode Failure;

l 但这种概率极小,不需要为极小概率事件调整JVM参数设置;

l 也没有必要修改执行多少次Full GC之后进行碎片清理,因为经过优化后, Full GC执行次数大大降低了。

那么订单系统的最终配置是:

JVM系统优化实践(8):订单系统的垃圾回收案例





感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~