简介:本文将探讨线上环境中频繁FGC(Full Garbage Collection)的原因,并通过实际操作与源码分析,揭示真凶——内存泄漏与不当的线程使用。我们将通过实例分析,提供可操作的建议和解决方法。
在软件开发中,FGC(Full Garbage Collection)是一个重要的性能指标。当FGC频繁发生时,它可能意味着应用程序的内存管理存在问题,这可能会导致应用性能下降,甚至服务中断。本文将通过一个真实的线上问题,深入解析频繁FGC的真凶,并提供一些解决建议。
上周,我们排查了一个线上问题,主要现象是CPU占用过高,JVM Old区占用过高,同时频繁发生FGC。在初步排查后,我们并未找到明显的原因,但在复查过程中,我们发现了一个令人惊讶的真凶。
首先,我们注意到在8.6分流量最高的这段时间,FGC的次数达到了惊人的70次。这明显超出了正常范围,按照经验,一个健康的应用甚至不应该一天FGC超过10次。这让我们开始怀疑是否存在内存泄漏或不当的线程使用。
接下来,我们查看了JVM线程数量。在流量冲入后,waiting的线程大量增加,几乎导致了Out of Memory(OOM)。在流量波峰过去后,线程数量又慢慢归于平稳。这进一步证实了我们的猜想:大量被创建的线程导致了内存飙升。
为了找出真正的罪魁祸首,我们将线程堆栈导入fastthread.io进行分析。在查看线程数最多的相同线程组时,我们发现了一个明显的异常:OkHttp的使用不当导致了OkHttp链接池的异常增多。我们初步推测,这可能是由于Feign底层使用了OkHttp,而OkHttp链接池创建异常导致的。
然而,经过进一步的调查,我们推翻了这个推测。首先,Feign不太可能存在这么明显的问题。其次,OkHttp链接池的异常增多并不一定是导致FGC频繁的直接原因。我们再次审查了代码和dump文件,最终发现了真凶——内存泄漏。
在dump文件中,我们注意到最大的对象是一个ArrayList,里面几乎都是ElasticSearchStatusException对象。这个异常的操作已经被我们定位到了,但是数据漏斗只有产品、运营等内部人员使用,按照使用频率推测,不应该有这么多对象。这说明在某个地方,ElasticSearchStatusException对象被不断创建并添加到ArrayList中,而没有被正确释放,导致了内存泄漏。
为了解决这个问题,我们进行了代码审查,并找到了问题所在。原来,在一个处理ElasticSearch响应的方法中,我们创建了一个ElasticSearchStatusException对象,并将其添加到ArrayList中。但是,在异常处理结束后,我们并没有从ArrayList中移除这个对象,导致ArrayList不断增长,最终导致了内存泄漏。
针对这个问题,我们修改了代码,确保在异常处理结束后,将ElasticSearchStatusException对象从ArrayList中移除。同时,我们还优化了OkHttp的使用,避免了链接池的异常增多。
经过修改后,我们再次进行了测试,发现FGC的次数明显降低,内存占用也趋于稳定。这说明我们已经成功解决了频繁FGC的问题。
总结起来,频繁FGC的真凶是内存泄漏和不当的线程使用。在解决这类问题时,我们需要通过代码审查、线程堆栈分析等手段,找出问题的根源,并采取有效的措施进行解决。同时,我们还需要注意优化代码和资源使用,避免类似问题的再次发生。
希望本文的分析和解决方案能对大家有所帮助。在实际开发中,如果遇到类似的问题,不妨尝试使用本文提供的方法进行排查和解决。相信在不断地学习和实践中,我们能够更好地掌握软件开发的技术和技巧。