JVM学习2021-03-05

案例分析:

案例背景:每日上亿请求量的电商系统,一般按照一个用户平均访问20次,那么上亿请求量,大致需要有500万日活用户。

按照10% 的付费转化率,每天有50w订单。50w订单在每天高峰期4个小时内,其实平均下来每秒也就是几十单。正常4C8G机器,一单算1KB,新生代需要很久才会满。

特殊的电商大促场景:类似双11场景,在10分钟内下单50w订单,平均每秒1000单。

抗住大促的瞬时压力需要几台机器:按照3台机器算,每台每秒300个下单请求,假设订单系统部署就是最普通的标配4核8G机器。从机器本身的CPU和内存资源角度,抗住每秒300个下单请求是没有问题的。但是问题就是在于需要对JVM有限的内存资源进行合理的分配和优化,包括对垃圾回收进行合理的优化,让JVM的GC次数尽可能的最少,而且尽量避免Full GC,这样可以尽可能减少JVM的GC对高峰期的系统更新难的影响。

大促高峰期订单系统的内存使用模型估计:基本按照一秒300下单请求,一单按照1kb估算,单单300订单就需要300kb的内存开销。算上订单对象连带订单条目对象、库存、促销、优惠卷等等一系列的业务对象,一般需要对单个对象开销放大10~20倍。

除了下单之外,这个订单系统还会有很多订单相关的其他操作,比如订单查询之类的,所以连带算起来,可以往大了估算,再扩大10倍的量。

那么每秒就会有300kb*20*10=60MB的内存开销,但是一秒过后,可以认为这60MB的对象就是垃圾了。因为这300个订单处理完了。所有相关对象都失去了引用,可以回收的状态。

内存该如何分配?:假设使用的4C8G的机器,那么JVM的内存一般会到4G,堆内存可以给3G,新生代可以给1.5G,老年代给到1.5G。

然后每个线程的Java虚拟机栈有1M,那JVM中几百个线程大约有几百M,再给永久代256M,基本上4G内存就差不多了。

同时设置一下参数”-XX:HandlePromotionFailure“选项,JVM参数如下所示:“-Xms3072M -Xmx3072M -Xmm1536M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:HandlePromotionFailure”,

但是“-XX:HandlePromotionFailure”参数在JDK1.6之后就被废弃了,所以现在一般都不会在生产环境里设置这个参数了,在JDK1.6之后只需要判断,老年代可用内存>新生代对象总和,或者    老年代可用内存>历次Minor GC 升入老年代平均的对象大小。两者满足一个,就可以进入Minor GC,不需要提前进行Full GC。

使用JDK1.7或JDK1.8参数保持如下即可:“-Xms3072M -Xmx3072M -Xmm1536M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M ”,

 

一秒过后会有60MB对象会变成垃圾,那么新生代1.5G内存空间大约需要25秒占满。然后进行Minor GC,此时有-XX:HandlePromotionFailure参数,直接比较“老年代可用内存>历次Minor GC 升入老年代平均的对象大小”,刚开始肯定能满足。一下子回收掉99%,此时存活对象为100MB左右。

问题来了,“-XX:SurvivorRatio”参数默认是8,那么新生代Eden区为1.2G,两个Survivor区为150MB。

1.2G差不多20秒Eden区就会满了,然后进行Minor GC。第一次存活对象100MB放在Survivor区,第二次Eden区满存活对象之前Survivor区存活对象100MB左右放到另外一块Survivor区。此时JVM参数:“-Xms3072M -Xmx3072M -Xmm1536M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8:”。

Survivor区只有150MB,每次Minor GC对象在100MB左右,很有可能超过,那么就会频繁让对象进入老年代,而且同龄对象超过Survivor区空间50%也会导致对象进入老年代。

所以建议调整新生代和老年代的大小。因为这种普通的业务系统,明显大部分的对象都是短生存周期的,根本不应该频繁进入老年代,没必要给老年代维持过大的内存空间,首先应该让对象尽量留在新生代。

可以考虑调整新生代为2G,老年代为1G。那么Eden区为1.6G,Survivor区为200MB,这样Survivor区变大,就大大降低新生代GC过后对象在Survivor放不下的问题,或者是同龄对象超过Survivor空间50%的问题。降低对象进入老年代的概率。

此时JVM参数如下:“-Xms3072M -Xmx3072M -Xmm2048M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8”

 

新生代对象躲过多少次垃圾回收后进入老年代?

“-XX:MaxTenuring Threshold”默认是对象躲过15次垃圾回收后会自动进入老年代。20秒触发一次Minor GC,如果连续躲过15次,那么一个对象在新生代也超过几分钟了,此时进入老年代也是应该的。

有的博客会说,应该提高这个参数,比如增加到20次,或者30次,其次那种说法根本不对的

因为这个参数必须结合系统的运行模型来说,如果躲过15次GC都已经几分钟了,一个对象几分钟都不能被回收,那说明肯定是系统里类似用@Service、@controller之类的注解标注的那种需要长期存活的核心业务逻辑组件。那么他们就应该进入老年代,阔且这种对象一般很少,一个系统累计也就几十MB而已。

甚至可以降低这个参数,在新生代停留超过1分钟,尽快就让他进入老年代。别在新生代里占着内存了。

此时参数为:“-Xms3072M -Xmx3072M -Xmm2048M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuring Threshold=5”

 

多大的对象直接进入老年代?

大对象可以直接进入老年代,因为大对象说明是要长期存活和使用的。

一般来说,设置1MB足以,因为一般很少超过1MB的大对象,如果有,可能是你提前分配了一个大的数组,大List之类的东西用来存缓存的数据。

此时JVM参数:“-Xms3072M -Xmx3072M -Xmm2048M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuring Threshold=5 -XX:PretenureSizeThreshold=1M”

 

别忘了指定垃圾回收器:

新生代使用ParNew,老年代使用CMS

JVM参数如下:“-Xms3072M -Xmx3072M -Xmm2048M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:SurvivorRatio=8 -XX:MaxTenuring Threshold=5 -XX:PretenureSizeThreshold=1M -XX:+UsePraNewGC -XX:UseConcMarkSweepGC”

 

ParNew垃圾回收器的核心参数,其实就是配套的新生代内存大小,Eden和Survivor的比例,只要设置合理,避免Minor GC 后对象放不下Survivor 进入老年代,或者是动态年龄判定之后进入老年代,给新生代里的Survivor充足的空间,那么Minor GC 就一直没什么问题。

然后根据你的系统运行模型,合理设置-XX:MaxTenuring Threshold参数,让那些长期存活的对象,抓紧尽快进入老年代,别在新生代里一直待着。

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值