背景
接手的服务在启动过程中总会发生young gc 超过100ms的异常告警,以下记录一下分析过程和定位解决的办法
原有的jvm参数配置如下
1 | /usr/local/java/bin/java |
从已有的参数只能看出来使用的是CMS gc,分析不出根因,所以还是要看gc的日志
第一次after gc ,from space剩余95%,times: 0.22 secs
1 | {Heap before GC invocations=0 (full 0): |
第二次 after gc, from space 剩余 100%,times: 0.47 sec
1 | {Heap before GC invocations=1 (full 0): |
第三次after gc,from space 剩余77%,times:0.17 secs
1 | {Heap before GC invocations=2 (full 0): |
第四次after gc,from space剩余25%,times:0.06
1 | {Heap before GC invocations=3 (full 0): |
可以看到,当fromspace要拷贝的更少的时候,所需要的gc时间则越短,所以推理猜测启动过程中是前几次GC时年轻代剩余的对象比较多,故复制算法导致耗时相对较高(具体的定位方法可以更换成G1 gc并增加-XX:+PrintReferenceGC参数)
增加参数后可以看到ref各阶段的耗时,比如单次gc 380ms,其中 object copy 142ms 和 ref proc的195 ms耗时占了大头,那这样就好解决了
[Ref Proc: 195.6 ms] 就是ref proc耗时长 ,再其中[FinalReference, 88104 refs, 0.1934312 secs] 占了0.19S
[Object Copy (ms): Min: 142.5, Avg: 152.2, Max: 156.6, Diff: 14.1, Sum: 608.7]
1 | 2023-04-25T20:10:10.201+0800: 7.862: [GC pause (G1 Evacuation Pause) (young)8.038: [SoftReference, 0 refs, 0.0001785 secs]8.038: [WeakReference, 5246 refs, 0.0013074 secs]8.040: [FinalReference, 88104 refs, 0.1934312 secs]8.233: [PhantomReference, 0 refs, 0 refs, 0.0000145 secs]8.233: [JNI Weak Reference, 0.0003759 secs], 0.3816639 secs] |
经验
- 启动过程object copy时间较久,是因为新生代使用的复制算法,拷贝的数据多,自然时间久。一种解决方法是更换gc处理器为G1,并且取消年轻代大小的控制,因为G1可以根据用户设置的暂停时间目标自动调整年轻代和总堆大小, 暂停目标越短年轻代空间越小(年轻代小,需要回收的空间可能就会小,但是回收次数也就会更多);整个年轻代内存会在初始空间-XX:G1NewSizePercent(默认整堆5%)与最大空间-XX:G1MaxNewSizePercent(默认60%)之间动态变化。年轻代空控制在合适的区间,自然可以缩短young gc的时间。
- finalizer ref 回收时间久则可以通过-XX:+ParallelRefProcEnabled解决。
结果
通过优化两个参数,启动从报young gc 100-300 ms 到50ms以下,符合优化预期。