记一次垃圾回收频繁问题解决

问题反馈

最近生产一台专门用于部署下载文件的服务运维反馈报垃圾回收频繁

情况描述

这种告警其实没有引起服务宕机,但是如果频繁的GC会影响服务接收请求的效率,减少服务可用的时间,进而影响整个服务的吞吐率。如果放任不管,也可能引起OOM。

排查思路

  1. 从告警信息"PSMarkSweep"得知垃圾清理使用的是"标记-清除"算法,之前学习JVM知道标记清除一般是用在老年代的,而老年代的垃圾回收一般都是和新生代一块回收的,这种回收会导致服务暂时无法使用也就是通常所说的STW。所以应该排查为什么内存这么容易就满了。

  2. 先看下在垃圾回收前,都有哪些请求会占用过多的资源,进行复现。

  3. 排查记录我看到,在这之前有用户频繁的在调用下载接口,所以可以得知肯定是用户调用才会引发GC,而且用户下载的都是EXCEL文件。那么就可以得知我们该如何复现场景了。

  4. 复现场景前,我们需要将垃圾回收日志打印出来。我在本地IDEA设置一下GC日志参数,看看垃圾回收的情况。

  5. 前期猜测是因为我们设置的堆内存大小和堆内存的分配策略有问题,于是我在本次进行项目启动,并打印垃圾回收的日志,打印出来出乎意料,几乎每秒都在执行垃圾回收。从日志可以看出来前期是因为初始化堆内存容量过小一直在扩容和垃圾回收。

  6. 打开jconsole观察垃圾回收的时间,确实报出来回收比较频繁,我的项目刚启动不到一个小时,光垃圾回收就用了19分钟。我决定先将堆内存的初始化大小设置大一点看看。

  7. 那么我的初始化容量大概是多少呢,查阅java官方(The Parallel Collector)得知

    1. 物理内存<=192MB的话JVM最大内存是物理内存的一半,否则占用物理内存的四分之一。

    2. 最大值、最小值、比例:

      a) 在32位JVM上,如果有4 GB或更多的物理内存,则默认的最大堆大小最多可以为1 GB。

      b) 在64位JVM上,如果有128GB或更多的物理内存,则默认的最大堆大小最大为32 GB。

      c) 在JVM初始化期间分配了一个较小的值,称为初始堆大小。此数量至少为8 MB,否则为物理内存的1/64,最大为1 GB。

  8. 分配给年轻代的最大空间量是堆总大小的三分之一,即年轻代和老年代默认的比例是1:2

  9. 观察垃圾回收日志,可知初始化和最大堆内存的大小(我的物理机器内存是32G,计算后初始化堆大小是32G/64=512M,最大堆内存是32G/4=8G)

  10. 上述可知,我初始化新生代是153088大概是150M,文件生成服务本身会将文件流读取到内存中,所以初始化内存不能过小,不然会导致频繁的JVM内存扩容。从而增加FULLGC的时间和扩容时间

  11. 故而我尝试将堆内存容量增大

  12. 继续重启后复现下载场景,重新观察GC日志

  13. 显然刚开始堆内存分配不足导致GC的日志没有了,继续观察,都是因为元空间内存不足导致的GC,先尝试把Metadata的空间设置大点看看。

  14. 设置完元空间的初始化内存后,继续重启观察GC日志,发现启动后只有一次新生代的GC,后面又频繁调用GC,我仔细观察了下GC的原因,这不是函数调用引起的么。谁没事干会手动调用垃圾回收啊,于是我在idea对System.gc()这个函数进行断点,发现了端倪。

  15. 观察调用链,发现jxl包在生成excel的时候后,每次关闭流的时候竟然会手动调用gc,我靠瞬间悟了。

  16. 继续观察发现有一个配置项可以设置disabled的状态,从而控制每次生成完excel关流时是否需要进行垃圾回收。

  17. 这个值可以通过JVM启动参数或者环境变量设置,这不找到原因了么,我赶紧加个配置试试

  18. 再次复现频繁下载场景,观察GC日志,已经只有少数新生代的GC信息了。

  19. 最后再观察下,垃圾回收时间明显减少了。到这基本上可以收工了。以后可以再探讨下,新生代回收频繁的问题,还有元空间之前设置的1024m是否合适,因为元空间毕竟只会初始化一些项目中的类信息和代码缓存等一些辅助信息。

小总结下

  • 频繁发生GC要先看下GC之前用户的行为,然后想办法进行复现

  • 排查问题时要善用GC日志

  • 如果项目初始化后内存不断扩大,可以考虑提升初始化的堆容量和元空间的容量

  • 观察GC日志中是否有手动调用GC,利用断点进行分析源头对其进行处理

相关推荐
王码码20352 小时前
Go语言的测试:从单元测试到集成测试
后端·golang·go·接口
王码码20352 小时前
Go语言中的测试:从单元测试到集成测试
后端·golang·go·接口
嵌入式×边缘AI:打怪升级日志3 小时前
使用JsonRPC实现前后台
前端·后端
小码哥_常3 小时前
从0到1:Spring Boot 中WebSocket实战揭秘,开启实时通信新时代
后端
lolo大魔王4 小时前
Go语言的异常处理
开发语言·后端·golang
IT_陈寒6 小时前
Python多进程共享变量那个坑,我差点没爬出来
前端·人工智能·后端
码事漫谈6 小时前
2026软考高级·系统架构设计师备考指南
后端
AI茶水间管理员7 小时前
如何让LLM稳定输出 JSON 格式结果?
前端·人工智能·后端
其实是白羊8 小时前
我用 Vibe Coding 搓了一个 IDEA 插件,复制URI 再也不用手动拼了
后端·intellij idea
用户8356290780518 小时前
Python 操作 Word 文档节与页面设置
后端·python