1. jvm调优思路

        jvm调优其实更多的是对GC的优化,尤其是尽量减少full GC。

        大多数情况下,对象在Eden区分配,当Eden区没有足够空间进行分配时,虚拟机将进行一次Minor GC ,可能有99%的对象被标记为垃圾被回收,剩余存活的对象会进入为空的survivor,下一次Eden区满了之后,又会触发minor gc,把Eden区和survivor区垃圾对象回收,把剩余存活的对象一次性挪动到另外一块为空的to区,因为新生代的对象都是朝生夕死的,存活时间很短,所以JVM默认的8:1:1的比例是很合适的,让eden区尽量的大,survivor区够用即可。

  • Minor GC/Young GC:指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。
  • Major GC/Full GC:一般会回收老年代 ,年轻代,方法区的垃圾,Major GC的速度一般会比Minor GC的慢 10倍以上。
  • Eden与Survivor区默认8:1:1

明白了上边的对象流转过程,我们可以在这个过程中做一些手脚,来进行jvm调优!

 

1.1 jvm调优方案

  • ①:设置大对象直接进入老年代! 大对象就是需要大量内存空间的对象(比如数组、字符串) ,通过jvm参数-XX:PretenureSizeThreshold 可以设置大对象的大小,如果对象超过设置大小会直接进入老年代,不会进入年轻代,这个参数只在 Serial 和ParNew两个收集器下有效。
    • 具体操作:-XX:PretenureSizeThreshold=1000000 (单位是字节) -XX:+UseSerialGC
    • 使用场景:当我们可以确定系统中的对象大部分为大对象,且短期内不会被垃圾回收,就可以根据对象大小设置jvm参数,让这些大对象直接进入老年代,省去了对象在新生代流转的过程,节省了Eden区的空间,因为大对象最终总会进入老年代的,还不如提前让出Eden空间,让他处理更多的小对象,提升系统性能!
  • ②:设置长期存活的对象提前进入老年代! 新生代的对象每熬过一次Minor GC ,其年龄就会+1,默认15岁,也就是流转15次就会进入老年代。当我们的系统中大概有大部分(80%)的对象都会经过15次Minor GC 进入老年代,我们可以通过设置-XX:MaxTenuringThreshold来调整进入老年代需要的年龄阈值。比如设置年龄为8即可进入老年代,这样那些长期存活的对象,就可以尽早的进入老年代,减少对象在新生代的流转次数,提升了系统性能!
  • ③:根据survivor区的动态年龄判断机制,合理设置新生代大小 。一般超过survivor区大小的60%会发生动态年龄判断机制,此时把最老的对象放进老年代。可以适当增加survivor区的大小避免Full GC!动态年龄判断的机制作用其实是希望那些可能是长期存活的对象,尽早进入老年代,避免多次复制操作而降低效率。

 

2. 订单的秒杀模块jvm调优案例

架构如下:
JVM调优思路、订单秒杀jvm调优案例_jvm

对亿级电商平台的调优中,首先要对自己的系统有足够的了解。根据以上架构:

  • ①:如果平台日活用户为500w,那大部分的付费转化率为10%,也就是每日50w单左右。
  • ②:如果50w单是在平时非促销的时候,下订单操作通过负载均衡打到服务器上,也就每秒几单、十几单的样子,服务器可以抗住。但如果要搞促销,50w单要在几分钟内产生,那么每秒钟就高达1000多单的交易,如果不合理设置jvm参数,就可能会频繁发生Full GC!
  • ③:假设有订单三台服务器,每秒1000单通过负载均衡到三台服务器,每台服务器每秒处理300单左右!每个订单对象的大小是由order类中的字段来决定的,假设每个订单对象有几十个字段,撑死1kb,每秒300kb对象生成,订单接口中肯定不止一个订单对象,还有库存、优惠券、积分等对象,所以300kb放大20倍,也就是6MB,同时还可能有别的操作,比如订单查询等,再放大10倍,也就是60MB,因为要保证请求速度,所以这60MB对象1s后都会成为垃圾。
  • ④:此时每秒60M对象进入堆内存。假如服务器内存是8G,给操作系统分配4-5G,剩下的分给JVM,因为 新生代:老年代 = 1:2,则新生代拿到1G,老年代2G,元空间512Mb,栈1M,jvm参数设置 如图所示!对象会首先进入Eden,800/60 ≈ 14秒 ,也即14秒时Eden区被放满,发生Minor GC!

JVM调优思路、订单秒杀jvm调优案例_jvm_02

  • ⑤:Minor GC会进行stw,前13秒的对象直接被gc掉。然而由于gc时的stw,用户线程无法执行,此时可能第14秒创建的对象会没有使用完,就会一直被引用着,GC完毕后,第14秒创建的对象由于被引用而存活了下来,此时存活的对象会进入survivor区。由于survivor区存在动态年龄判断机制:如果Survivor区中的这批对象总大小大于Survivor区域内存大小的50%,那么>=这批年龄最大值的对象,都会被放入老年代。 该组对象大小>50MB,年龄都是1,所以此时第14秒这批对象都会直接进入老年代。也就是每14秒60M对象进入老年代!导致几分钟一次GC!
  • ⑥:由于上述原因就是由survivor区存在动态年龄判断机制导致的,所以我们可以通过调整新生代大小来避免,对象进入老年代!
    JVM调优思路、订单秒杀jvm调优案例_jvm_03
    把年轻代分配2G,则survivor区为200M,60M的对象进来不至于触发动态年龄判断机制,一次Minor GC就杀死了所有对象,几乎没有对象进入老年代,解决了Fulle GC频繁的问题!

结论:通过上面这些内容介绍,大家应该对JVM优化有些概念了。

  • 尽可能让对象都在新生代里分配和回收,尽量别让太多对象频繁进入老年代,避免频繁对老年代进行垃圾回收。
  • 同时给系统充足的内存大小,避免新生代频繁的进行垃圾回收。