问题描述:在进行双十一备战的过程中,发现JVMyoung GC的频次很高,同时一次Young Gc的耗时在500ms左右,FullGC的在1-2天触发一次
JVM原配置:-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=85 -XX:+UseCMSInitiatingOccupancyOnly -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=52001 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false
优化方向:关注Young GC ,FullGC触发次数可不考虑
(1)、考虑到这个应用是一个WEB应用,同时也是MQ的消费端,应该第一个原因就是MQ的消费会导致大量的临时对象产生,YOUNG GC频繁,同时在接受商品上下架(全站)、收银台对账消息,每个都在3000/TPS的量,峰值能达到10000+。所以第一个优化思路,是将MQ消费应用,与WEB应用分离,减少之间的影响。
优化完,MQ消费应用YOUNGGC频次同之前,WEB应用YOUNGGC2分钟一次,每次GC耗时:50ms
总结:资源隔离,保护线上稀有资源,是针对系统的优化需要关注的点,此时针对于YOUNG GC已到达本身的系统要求,针对于MQ消费者应用,YOUNGGC频次高一些,对于消费应用的延迟来说还是可接受的。但是还是想搞清楚时间消耗在哪里?
(2)YoungGC时间消耗:
1.Root Scanning
2.Object Copy
GC roots 包含以下引用:
1.所有java线程以及线程栈帧里指向GC堆里的对象的引用;
2.JNI Local & Global;
3.由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的;
4.stack local Java方法的local变量或参数;
5.其他,包含monitor & finalizable & native stack
排查方法,打印各种日志。
-XX:+PrintGCDetails -Xloggc:/export/Logs/gc.log 打印日志,,
XX:+PrintReferenceGC 打印Reference回收消耗时长
-XX:+PrintGCApplicationStoppedTime -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1 打印安全点时间消耗
-XX:+PrintTenuringDistributio 打印年龄分布(注意动态年龄计算)
查看日志后发现,这几个的时间跟500ms都不是一个量级的,所以根本原因不在这里。
后面考虑到没有设置GC回收线程数量,通过DUMP、JCMD命令可以发现GC线程数量远远超过服务器核数,(4C8G服务器配置)
①如果用户显示指定了ParallelGCThreads,则使用用户指定的值。
②否则,需要根据实际的CPU所能够支持的线程数来计算ParallelGCThreads的值,计算方法见步骤③和步骤④。
③如果物理CPU所能够支持线程数小于8,则ParallelGCThreads的值为CPU所支持的线程数。这里的阀值为8,是因为JVM中调用nof_parallel_worker_threads接口所传入的switch_pt的值均为8。
④如果物理CPU所能够支持线程数大于8,则ParallelGCThreads的值为8加上一个调整值,调整值的计算方式为:物理CPU所支持的线程数减去8所得值的5/8或者5/16,JVM会根据实际的情况来选择具体是乘以5/8还是5/16。
比如,在64线程的x86 CPU上,如果用户未指定ParallelGCThreads的值,则默认的计算方式为:ParallelGCThreads = 8 + (64 - 8) * (5/8) = 8 + 35 = 43。
ParallelGCThreads= 8 + (N - 8) * 5 / 8
手动设置,ParallelGCThreads= 4
YoungGC耗时变成75ms左右。
此时大部分GC耗时已经解决,继续优化,
1.发现安全点时间耗时都是几ms,故排除这个优化点
2.finalReference耗时在 20-40ms左右,开启-XX:+ParallelRefProcEnabled,并行回收,优化到60ms左右
3.打印年龄代分布,可以看到age1 age2差距比较大,应该大多在第一次younggc就可以回收的对象,在eden区又copy到 s0区,减少,copy,可以稍微增加xmn设置大小,(测试了1G 1.5G 2G)根据不同系统可能不同,最终选择2G在频次和耗时之间折中选择。
备注:
AGE年龄优化分析:
Eden移动到Survivor情况:假设机器2 Eden区域是 机器1 的两倍大,其他条件都保持不变。就一般情况来说(Survivor区域中存活的对象比Eden少很多,比如1%),那么机器1 young gc的频率是 机器2 young gc 频率的2倍;那么假设机器1在T时间内GC一次,在GC之后由Eden区域晋升到Survivor的大小为10M(即age=1),那么机器2在2T时间之后发生GC,1T-2T之间生成的对象和机器1类似,GC之后有10M进入Survivor区域,但是0T-1T内最多会剩余10M内存可能会进入到Survivor,但是在经历1T-2T时间之后也有可能导致object已经不存活,如何判断这部分对象有没有存活呢,在机器1在2T的时间点要又要进行一次young gc,那么在0T-1T之前存活的对象也就是age=1的对象将会再次会经历一次young gc,便是了age=2,所有看age=2的年龄段剩余多少就可以了。机器2一次GC之后,由Eden区域进入到Survivor区域中的大约等于10M+机器1中Survivor中(age=2)也就是机器1:age1+age2中的object对象。
-Xmn2048M -XX:ParallelGCThreads=4 -XX:+ParallelRefProcEnabled -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=85 -XX:+UseCMSInitiatingOccupancyOnly -XX:MaxTenuringThreshold=15 -XX:+PrintGCDateStamps -XX:+PrintGCDetails -Xloggc:/export/Logs/gc.log -XX:+PrintReferenceGC -XX:+PrintTenuringDistribution -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=52001 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false