记一次线上Groovy导致的OOM的问题解决过程
jobmgr模块是一个任务执行框架,里面有一个定时任务,定时从数据库读取最新的script,并通过groovy load成class,以此实现动态更新脚本。 代码如下: @Service("jobTemplateClassService")
public class JobTemplateClassServiceImpl extends ScheduledTaskExecutor implements JobTemplateClassService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Value("${galileo.api.endpoint}")
private String endPoint;
private String jobTemplateScriptListApi = "/job_template_script/list";
private String jobTemplateScriptGetApi = "/job_template_script/get";
private Map<String,Class<JobTemplate>> jobTemplateScriptMap = new HashMap<>();
@Autowired
private JobTemplateClassFactory jobTemplateClassFactory;
@Override
public Class<JobTemplate> getJobTemplateClass(String jobTemplateScriptUuid) {
Class<JobTemplate> jobTemplateClass = null;
if (null != jobTemplateScriptMap) {
jobTemplateClass = jobTemplateScriptMap.get(jobTemplateScriptUuid);
}
if (null == jobTemplateClass) {
JobTemplateScript jobTemplateScript = HttpClient.get(HttpUtils.buildRequestUrl(endPoint,jobTemplateScriptGetApi,new Parameter("jobTemplateScriptUuid",jobTemplateScriptUuid)),new TypeReference<JobTemplateScript>() {
});
if (null == jobTemplateScript) {
return null;
}
jobTemplateScriptMap.put(jobTemplateScriptUuid,jobTemplateClassFactory.createJobTemplateClass(jobTemplateScript.getScript()));
}
return jobTemplateClass;
}
@Override
protected void scheduledTask() {
try {
List<JobTemplateScript> jobTemplateScripts = HttpClient.get(HttpUtils.buildRequestUrl(endPoint,jobTemplateScriptListApi),new TypeReference<List<JobTemplateScript>>(){});
Map<String,Class<JobTemplate>> newJobTemplateScriptMap = new HashMap<>();
for (JobTemplateScript jobTemplateScript : jobTemplateScripts) {
newJobTemplateScriptMap.put(jobTemplateScript.getUuid(),jobTemplateClassFactory.createJobTemplateClass(jobTemplateScript.getScript()));
}
logger.debug("JobTemplateClassService old map size : {}",this.jobTemplateScriptMap.size());
//指向新的缓存对象 旧的对象无引用自动回收
this.jobTemplateScriptMap = newJobTemplateScriptMap;
logger.debug("JobTemplateClassService new map size : {}",this.jobTemplateScriptMap.size());
} catch (RPCCallException e) {
logger.error("Failed to init template script,galileo.api is not started?",e);
} catch (Exception e) {
logger.error("Failed to init template script",e);
}
}
}
问题描述:在线上运行了将近半个月后,jobmgr模块OOM了
猜测是类加载,没有卸载干净导致类无限制增长。 问题解决1.先确认问题确实出在此处本地debug,使用jconsole观察类及PermGen内存使用情况.(本机jdk1.8因此是metaspace) 通过观察发现,经过20分钟过后,两个地方都依然稳定的增长,并且是和任务执行周期一致,一分钟增长一次。基本确定问题出在此处。 修改scheduled部分代码: @Override
protected void scheduledTask() {
if(!scriptMap.isEmpty()) {
return;
}
/* * */
}
再次观察,约几分钟后,类趋于稳定,不再增长,至此确定问题所在。 2.追根溯源启动VM参数加上-verbose查看类加载及卸载信息。 可以看到,每次更新脚本script,只会load class,但是没有unload.
猜测是groovy的类加载方式引起的旧map无法回收。查资料。 这个博主讲的很详细,Very Good!但是看完发现问题不应该是groovy load()更新class的锅。试验一下.只加载类,无任何引用. @Override
protected void scheduledTask() {
try {
List<JobTemplateScript> jobTemplateScripts = HttpClient.get(HttpUtils.buildRequestUrl(endPoint,this.jobTemplateScriptMap.size());
//指向新的缓存对象 旧的对象无引用自动回收
//this.jobTemplateScriptMap = newJobTemplateScriptMap;
newJobTemplateScriptMap = null;
logger.debug("JobTemplateClassService new map size : {}",e);
}
}
发现问题依旧!!! 继续查资料 这个BUG已经被修复,修复版本为2.4.8,看了下我们groovy版本 <groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>2.4.7</version>
都是雷坑啊!!!!赶紧升级了看看. 升级完后,问题依旧。。。于是跟进开发人员2.4.8提交的看。说是确实修复了无法unload class的问题,这里写链接内容 另外,参考大神的写法,每次load后clearCache() //注意 不要一parse就clear,这时候新的还没put进map。
在完全load并put进缓存之后再clearCache。或者不加这一句
finally {
groovyClassLoader.clearCache();
}
总结:此次BUG是因为groovy本身存在无法卸载class的bug导致。(持有一个强引用?) 经过线上验证,发现确实升级后此BUG已经解决。 后记:最开始猜测是这样更新map不合理导致的。 另外就是解决这种问题很有意思。 以后自己写程序一定要注意内存泄漏的问题! 参考:JAVA内存和GC机制 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |