默认的JVM参数绝对是系统负载逐渐增高的时候一个最大的问题
如果你不设置-Xmx、-Xms之类的堆内存大小的话,你启动一个系统,可能默认就给你几百MB的堆内存大小,新生代和老年代可能都是几百MB的样子。新生代内存过小,会导致Survivor区域内存过小,同时Eden区域也很小。Eden区域过小,自然会导致频繁的触发Young GC,Survivor区域过小,自然会导致经常在Young GC之后存活对象其实也没多少,但就是Survivor区域放不下。此时必然会导致对象经常进入老年代中,因此也必然会导致老年代过一段时间就放满了,然后就会触发Full GC。
因此在大部分工程师都对JVM优化不是很精通的情况下,而你作为技术Leader或架构师,通过推行一个JVM参数模板,可以让各个系统短时间内迅速优化JVM的性能。
这里强调一点:没有绝对的模板,任何系统的实际运行情况以及生产环境都不一样,因此不能直接想着万能模板解决,而是真正自己通过分析JVM的运行情况,内存占比,GC频率去准确的进行优化!
这里我们仅仅是针对中小型公司,一些完全不懂JVM参数优化的团队,给出一个标准的模板进行优化,总比直接使用默认JVM参数要好很多!然后尽量让大部分系统套用这个模板,基本保证JVM性能别太差,避免很多初中级工程师直接使用默认的JVM参数,可能一台8G内存的机器上,JVM堆内存就分配了几百MB。
模板参数:
-Xms4096M
-Xmx4096M
-Xmn3072M
-Xss1M
-XX:PermSize=256M
-XX:MaxPermSize=256M
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=92
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
参数设置解释:
一般来讲线上部署的机器都差不多是8G4核,那么分配4G给JVM内存也就足够了,毕竟还有其他一些进程需要使用到内存,然后新生代分配了3G,尽量让新生代大一些!进而对应的Survivor区域能分配到300MB。
不同的系统运行的状况不同,基本上每次minor gc 过后存活几十MB对象是常态,因此几十MB对象进入Survivor区还是很轻松的,也不会轻易触发 动态年龄判定规则,让部分对象直接进老年代。
只要内存分配合理,那么进入老年代的对象就很少或极慢,该参数模板在多个系统的测试下,通过jstat观察,基本上各个系统的Full GC都在几天发生一次。
-XX:CMSFullGCsBeforeCompaction=0该参数的设置也是非常重要,保证了我们每次Full GC之后都会执行一次内存整理,解决内存碎片的问题。
进一步性能优化:
这里我们再介绍两个参数,可以帮助优化Full GC的性能,把每次Full GC的时间进一步降低一些。
第一个参数:-XX:+CMSParallelInitialMarkEnabled,这个参数会在CMS垃圾回收器的“初始标记”阶段开启多线程并发执行。
因为初始标记阶段,我们之前说过是会进入STW的,导致系统停顿,所以这个阶段如果能多线程并发执行,可以有效提升标记的效率,减少STW的时间。
第二个参数:-XX:+CMSScavengeBeforeRemark,这个参数会在CMS的重新标记阶段之前,先尽量执行一次Young GC。
CMS的重新标记阶段也是会进入STW的,所以如果在重新标记之前,先执行一次Young GC,就会回收掉一些年轻代里没有人引用的对象。所以如果先提前回收掉一些对象,那么在CMS的重新标记阶段就可以少扫描一些对象,此时就可以提升CMS的重新标记阶段的性能,减少他的耗时。
加入两个参数后的整体JVM参数模板:
-Xms4096M
-Xmx4096M
-Xmn3072M
-Xss1M
-XX:PermSize=256M
-XX:MaxPermSize=256M
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=92
-XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction=0
-XX:+CMSParallelInitialMarkEnabled
-XX:+CMSScavengeBeforeRemark
ok,那么以上JVM参数模板就给到大家了,后续开发中可以直接进行使用。哪怕是不太懂JVM优化的普通工程师只要套用这个模板,对一些普通的业务系统,都能保证其JVM性能不会出现大的问题,比如频繁的Young GC和Full GC导致的系统频繁卡顿。