您好,我是湘王,这是我的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(垃圾)。
按此消费水平,内存模型是:
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秒后就会被填满。
所以稍稍调整下,增加-XX:SurvivorRatio参数:
-Xms3072M -Xmx3072M -Xmn1536M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:SurvivorRatio=8
-XX:SurvivorRatio=8,导致Eden只有1.2G,会提前触发Minor GC:
接下来可以再考虑优化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执行次数大大降低了。
那么订单系统的最终配置是:
感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~