前言
和朋友聊天时,他讲了他遇到的一个了一个生产环境的 JVM GC 问题,很典型,在这里整理记录一下。
问题描述
线上应用,很久没发版本,跑了很长时间没问题。忽然有一天,CPU 使用率非常高,接口开始陆续超时。
观察 JVM 监控,发现发生了很多次 Full GC。
问题分析
Full GC,JVM 堆内存老年代的空间不够了,才会进行 Full GC,为什么堆内存会不够呢?经过排查,是很多大内存的对象没有释放。
但为什么没有释放呢?是线程池上下文切换,一直保有了大对象的引用,导致大对象无法回收,占用内存无法释放。
具体的过程是这样的:这段业务逻辑会启动线程池进行处理,但线程池的核心线程数不够,之前业务量没那么大的时候,是没有问题的,但业务量变大之后,来不及处理的任务积压在线程池的队列中,这些积压的任务又保有了外部的大对象的引用,导致外部的对象无法被回收。如果核心线程的数量较少,而队列过长,没有新建线程,那么随着队列中任务越来越多,大对象就会使用越来越多的无法回收的堆空间,进而导致 Full GC。
解决方案
- 增大核心线程数量
- 如果不是同步的,不使用线程池,发 Queue 给自己处理
- 减少队列长度,这个可能还需要对最大线程数量,拒绝策略等进行调整
总结
一个问题涉及到了 线程池,闭包,JVM Full GC,真的很神秘。我后续又查了查 Full GC 还有什么可能的原因,大部分平时查不出来,关键时候又会爆出来的就是大对象了,其他的还有这些可能:
- 内存泄漏
- 数据库查询没加条件,导致查询出全表数据
- 代码中有 System.gc()
- 长时间持有大对象,或者持有太多大对象
基本就是这些,这种 JVM 的问题也是很棘手而且少见。