在一次压测时,发现应用做了大量的 YongGc。
通过日志可以看到触发 YongGc 的有两种途径:
Allocation Failure
GCLocker Initiated GC
日志详情:
2020-05-20T19:54:57.647+0800: 351449.612: [GC (Allocation Failure) 2020-05-20T19:54:57.647+0800: 351449.612: [ParNew
Desired survivor size 8716288 bytes, new threshold 15 (max 15)
- age 1: 118080 bytes, 118080 total
- age 2: 83272 bytes, 201352 total
- age 3: 23904 bytes, 225256 total
- age 4: 29168 bytes, 254424 total
- age 5: 86944 bytes, 341368 total
- age 6: 89744 bytes, 431112 total
- age 7: 114144 bytes, 545256 total
- age 8: 11400 bytes, 556656 total
- age 9: 26056 bytes, 582712 total
- age 10: 162856 bytes, 745568 total
- age 11: 30648 bytes, 776216 total
- age 12: 25960 bytes, 802176 total
- age 13: 15552 bytes, 817728 total
- age 14: 82336 bytes, 900064 total
- age 15: 175536 bytes, 1075600 total
: 138365K->1989K(153344K), 0.0170535 secs] 1094019K->957739K(2080128K), 0.0172034 secs] [Times: user=0.03 sys=0.00, real=0.01 secs]
通过阅读日志可以得到的信息为:
1)Allocation Failure:年轻代中没有足够的空间能够存储新的数据了。
2)年轻使用的是 ParNew 垃圾收集器。ParNew 是一个 Serial 收集器的多线程版本,会使用多个 CPU 和线程完成垃圾收集工作(默认使用的线程数和 CPU 数相同,可以使用-XX:ParallelGCThreads 参数限制)。该收集器采用复制算法回收内存,期间会停止其他工作线程,即 Stop The World。
3)Desired survivor size 8716288 bytes, new threshold 15 (max 15) 期望存活 8M 的内存,JVM 内部计算出的晋升阈值最大值为 15。同时可以看到不同晋升阈值的内存大小。
4)138365K->1989K(153344K) 138365 千字节=135.12 兆字节 133.18 兆
当前的年轻代占用的大小为 135M YongGc 后内存大小为 1989 千字节=1.94 兆字节 实际年轻代的总存储为 153344 千字节=149.75 兆字节 YongGc 耗时 17ms
5) 1094019K->957739K(2080128K), 0.0172034 secs 三个参数分别为:堆区垃圾回收前的大小 (1094019 千字节=1068.37 兆),堆区垃圾回收后的大小 (957739 千字节=935.29 兆),堆区总大小 (2080128 千字节=2031.375 兆字节)。减少 133.08 兆。 说明有 0.1 兆晋升到了老年代。
对 GC 日志进行的数据统计。
来看一下参数配置:
-XX:MaxDirectMemorySize=1G
-XmX2G
-XmS2G
# CMS GC config
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=70
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+PrintTenuringDistribution
-XX:MaxTenuringThreshold=15
# GC log
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=2
-XX:GCLogFileSize=128M
-Xloggc:/var/lib/jetty/logs/gc.log
# other config
-XX:+PrintCommandLineFlags
-XX:-OmitStackTraceInFastThrow
-XX:+HeapDumpOnOutOfMemoryError
# -XX:+HeapDumpBeforeFullGC
# -XX:+HeapDumpAfterFullGC
-XX:HeapDumpPath=/var/lib/jetty/logs/dump.hprof
-Dfile.encoding=UTF-8
那么既然推断年轻代的大小过小。目前通过配置的参数没有设置年轻代,那么目前年轻代的大小为多大呢,且设置多少合适呢。
通过目前的日志可以看到年轻代的大小为 135M 左右。
那么这个值是怎么算出来的呢?
Sun 官方建议年轻代的大小为整个堆的 3/8 左右, 所以按照上述设置的方式,可以设置为 768M。
可以通过设置年轻代大小后,再次观察 yonggc 的次数和 yonggc 平均耗时、总耗时来决定这个参数是否可行。
JVM 参数优化的目的就是降低 gc 频率、GC 平均耗时和总耗时。从而达到降低程序停顿的时长。
同时可以看下通过调整参数后。gc 吞吐率是否上升了。如果上升了也从侧面反映 gc 的频率和耗时降低了。
以上观点纯属个人认识,如有问题欢迎留言讨论。