一、 背景描述
某个应用在生产环境通过系统监控发现,应用每隔10小时就会触发一次Full GC,该系统当时承接的业务量并不大,而且固定10小时就会进行Full GC,通过监控时间轴发现Full GC频率很规律,直觉告诉我这不是JVM自身触发的Full GC操作,应该是某个定时任务中进行了垃圾回收操作,但是什么业务场景会存在这种情况呢?
二、问题排查
通过在测试环境上通过系统监控可以发现,应用的JVM使用情况并未达到Full GC条件,是系统调用了System.gc()
方法产生的Full GC。于是,我在代码中对System.gc()
方法调用进行了搜索,甚至为了防止是通过反射方式调用的,还检索了所有业务代码,但是均未发现有业务代码进行该方法的调用。
不过,在排查过程中,Jdk中有一个GC类却引起了我的注意:
在Java中Daemon
一般用作标识守护线程,这里有一个Daemon类,是不是因为在这个线程类中调用System.gc()方法的原因呢?
查看sun.misc.GC.Daemon
类的源码:
通过源码可以发现,Daemon
类继承了Thread
线程类,并且有调用System.gc()
方法。
同时,在应用中,通过jstack
命令查看,应用中确实有此线程运行:
通过对代码进行断点调试,发现Apache cxf相关类会有周期性Full GC的问题,我引入的相关依赖如下:
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-spring-boot-starter-jaxws</artifactId>
<version>3.3.11</version>
</dependency>
通过对源码解析发现,最终调用的情况如下:
org.apache.cxf.common.logging.LogUtils
org.apache.cxf.common.logging.JDKBugHacks
注意这里:skipHack()
方法的判断逻辑,通过这部分代码可以设置参数跳过执行创建守护线程的逻辑。
三、原因分析
源码解析流程如下:LogUtils
类加载时会通过静态代码块调用JDKBugHacks.doHacks()
方法,此方法会获取系统环境变量org.apache.cxf.JDKBugHacks.gcRequestLatency
,如果没有设置,默认为false,此时会通过反射调用sun.misc.GC
的requestLatency
方法,
初始化的时候deamon对象的值为null,此时会调用Daemon.create()
方法,如下:
在create()
方法中,会创建一个名字为 GC Daemon
的守护线程,如下:
四、问题解决
Apache CXF是一个开源的Service框架,它实现了JCP和Web Servicez中的一些重要标准,大大简化了Web Service服务构建开发工作,通过上述对org.apache.cxf.common.logging.JDKBugHacks
源码分析,可以在项目启动脚本中加入
-Dorg.apache.cxf.JDKBugHacks.gcRequestLatency=true
来跳过创建守护线程的逻辑,解决固定Full GC的逻辑。