一、现象:
每天早上业务使用高峰期,出现频繁minorGC,过一段时间后,服务正常。
运行一周左右出现oldGC资源回收效果很小,频繁出现oldGC,重启后内存占用下降。
二、查找过程:
1.查看内存情况
free -m
内存并未超预期,当oldGC报警时,内存都被占用。
2.查看gc回收情况
获取进程号:jps
查看回收情况:jstat -gcutil <进程号> 2000
3.查看堆栈占用情况
jmap -histo:hive <进程号> > jmap.info
三、根据查找过程数据分析:
1.查看jmap.info占用比较高的那些对象,发现:org.springframework.cloud.sleuth.Span 特别多。正常应该每次请求完,就会释放span对象,新生代也不会堆积该对象。
2.查找代码中相关的span代码,发现如下代码
Span tmpSpan = tracer.createSpan(module.getName(), span);
查看源码,每次新建span的时候都会往spanContextHolder里加入一个不会自动关闭的span
ThreadLocal<SpanContext> CURRENT_SPAN = new NamedThreadLocal<>
static void push(Span span, boolean autoClose) {
if (isCurrent(span)) {
return;
}
CURRENT_SPAN.set(new SpanContext(span, autoClose));
}
其中使用的ThreadLocal<SpanContext>,由于项目中使用的是线程池,线程会被循环使用。所以这个新创建的span就不释放。
四、解决:
1.找到所有使用到的createSpan的地方,进行显示的关闭span
tracer.close(tmpSpan);
五、后续:
将这个上线之后,发现span的数量已经从jmap中的前几名中消失了。
但发现早上业务高峰期查询比较多的时候,还是会有短时间的minorGC频繁的报警。再次查找代码发现:
ThreadPoolExecutor WORK_COUNT_EXECUTOR = new ThreadPoolExecutor(20, 200,
1L, TimeUnit.HOURS, new LinkedBlockingDeque<>(100), new MyThreadFactory("xxxx"));
使用的线程池最大线程数设置的比较大,就是说在请求量上来的时候,span的数量会是正常情况下的10倍。
六、在优化:
1.调低线程池最大线程数的数量,同时,分析业务场景,增大新生代的大小。
总结:
1.在使用新的对象的时候,要多阅读相关使用手册,正确使用。
2.gc问题是一个持续解决、持续优化的过程,不要要求一次就能完美解决。而且需要持续跟进。
3.遇到内存或cpu问题,莫慌!先收集现场信息,及时恢复服务使用。线下认真分析,相信自己且多和同时讨论分析。