一、現象:
每天早上業務使用高峯期,出現頻繁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問題,莫慌!先收集現場信息,及時恢復服務使用。線下認真分析,相信自己且多和同時討論分析。