一次 JVM Full GC 问题的记录

2023/03/19 Java solution 共 677 字,约 2 分钟

前言

和朋友聊天时,他讲了他遇到的一个了一个生产环境的 JVM GC 问题,很典型,在这里整理记录一下。

问题描述

线上应用,很久没发版本,跑了很长时间没问题。忽然有一天,CPU 使用率非常高,接口开始陆续超时。

观察 JVM 监控,发现发生了很多次 Full GC。

问题分析

Full GC,JVM 堆内存老年代的空间不够了,才会进行 Full GC,为什么堆内存会不够呢?经过排查,是很多大内存的对象没有释放。

但为什么没有释放呢?是线程池上下文切换,一直保有了大对象的引用,导致大对象无法回收,占用内存无法释放。

具体的过程是这样的:这段业务逻辑会启动线程池进行处理,但线程池的核心线程数不够,之前业务量没那么大的时候,是没有问题的,但业务量变大之后,来不及处理的任务积压在线程池的队列中,这些积压的任务又保有了外部的大对象的引用,导致外部的对象无法被回收。如果核心线程的数量较少,而队列过长,没有新建线程,那么随着队列中任务越来越多,大对象就会使用越来越多的无法回收的堆空间,进而导致 Full GC。

解决方案

  1. 增大核心线程数量
  2. 如果不是同步的,不使用线程池,发 Queue 给自己处理
  3. 减少队列长度,这个可能还需要对最大线程数量,拒绝策略等进行调整

总结

一个问题涉及到了 线程池,闭包,JVM Full GC,真的很神秘。我后续又查了查 Full GC 还有什么可能的原因,大部分平时查不出来,关键时候又会爆出来的就是大对象了,其他的还有这些可能:

  1. 内存泄漏
  2. 数据库查询没加条件,导致查询出全表数据
  3. 代码中有 System.gc()
  4. 长时间持有大对象,或者持有太多大对象

基本就是这些,这种 JVM 的问题也是很棘手而且少见。

文档信息

Search

    Table of Contents