一场FullGC故障排查

简介: 本文记录了一次JDOS容器CPU告警的排查过程,通过分析发现实际为JVM Full GC引发CPU占用升高。结合泰山与SGM监控,定位到堆内存中大对象导致老年代频繁占满。经JPofiler分析,确认问题源于将Excel数据以List<Map<String, String>>形式加载至内存,造成严重内存膨胀。最终提出优化方案:避免大对象驻留JVM或改用高效存储结构,降低GC压力。

一、问题发现与排查
1.1 找到问题原因
问题起因是我们收到了jdos的容器CPU告警,CPU使用率已经达到104%

观察该机器日志发现,此时有很多线程在执行跑批任务。正常来说,跑批任务是低CPU高内存型,所以此时考虑是FullGC引起的大量CPU占用(之前有类似情况,告知用户后重启应用后解决问题)。
通过泰山查看该机器内存使用情况:

可以看到CPU确实使用率偏高,但是内存使用率并不高,只有62%,属于正常范围内。
到这里其实就有点迷惑了,按道理来说此时内存应该已经打满才对。
后面根据其他指标,例如流量的突然进入也怀疑过是jsf接口被突然大量调用导致的cpu占满,所以内存使用率不高,不过后面都慢慢排除了。其实在这里就有点一筹莫展了,现象与猜测不符,只有CPU增长而没有内存增长,那么什么原因会导致单方面CPU增长?然后又朝这个方向排查了半天也都被否定了。
后面突然意识到,会不会是监控有“问题”?
换句话说应该是我们看到的监控有问题,这里的监控是机器的监控,而不是JVM的监控!
JVM的使用的CPU是在机器上能体现出来的,而JVM的堆内存高额使用之后在机器上体现的并不是很明显。
遂去sgm查看对应节点的jvm相关情况:

可以看到我们的堆内存老年代确实有过被打满然后又清理后的情况,查看此时的CPU使用情况也可以与GC时间对应上。
那么此时可以确定,是Full GC引起的问题。
1.2 找到FULL GC的原因
我们首先dump出了gc前后的堆内存快照,
然后使用JPofiler进行内存分析。(JProfiler是一款堆内存分析工具,可以直接连接线上jvm实时查看相关信息,也可以分析dump出来的堆内存快照,对某一时刻的堆内存情况进行分析)
首先将我们dump出来的文件解压,修改后缀名.bin,然后打开即可。(我们使用行云上自带的dump小工具,也可以自己去机器上通过命令手工dump文件)

首先选择Biggest Objects,查看当时堆内存中最大的几个对象。
从图中可以看出,四个List对象就占据了近900MB的内存,而我们刚刚看到堆内存最大也只有1.3GB,因此再加上其他的对象,很容易就会把老年代占满引发full gc的问题。

选择其中一个最大的对象作为我们要查看的对象
这个时候我们已经可以定位到对应的大内存对象对应的位置:

其实至此我们已经能够大概定位出问题所在,如果还是不确定的话,可以查看具体的对象信息,方法如下:

可以看到我们的大List对象,其实内部是很多个Map对象,而每个Map对象中又有很多键值对。
在这里也可以看到Map中的相关属性信息。
也可以在以下界面直接看到相关信息:

然后一路点下去就可以看到对应的属性。
至此,我们理论上已经找到了大对象在代码中的位置。
二、问题解决
2.1 找到大对象在代码中的位置与问题的根本原因
首先我们根据上述过程找到对应位置与逻辑
我们的项目中大概逻辑是这样的:

  1. 首先会解析用户上传的Excel样本,并将其加载到内存中作为一个List变量,即我们上述看到的变量。一个20w的样本,此时字段数量有a个,大概占用空间100mb左右。
  2. 然后遍历循环用户样本,根据用户样本中的数据,再增加一些额外的请求数据,根据此数据请求相关结果。此时字段数量有a+n个,占用空间已经在200mb左右。
  3. 循环完成后将此200mb的数据存入缓存。
  4. 开始生成excel,将200mb数据从缓存中取出,并根据之前记录的a个字段,取出初始的样本字段填充至excel。
    用流程图表示为:

结合一些具体排查问题的图片:

其中一个现象是每次gc后的最小内存正在逐步变大,对应上述步骤中第二步,内存正在逐步膨胀。
结论:
将用户上传的excel样本加载到内存中,并将其作为一个List>的结构存储起来,首先一个20mb的excel文件以此方式存储会膨胀占用120mb左右堆内存,此步骤会大量占用堆内存,并且因为任务逻辑原因,该大对象内存会在jvm中存在长达4-12小时之久,导致一但任务过多,jvm堆内存很容易被打满。
这里列举了为什么使用HashMap会导致内存膨胀,其主要原因是存储空间效率比较低:
一个Long对象占内存计算:在HashMap结构中,只有Key和Value所存放的两个长整型数据是有效数据,共16字节(2×8字节)。这两个长整型数据包装成java.lang.Long对象之后,就分别具有8字节的MarkWord、8字节的Klass指针,再加8字节存储数据的long值(一个包装对象占24字节)。
然后这2个Long对象组成Map.Entry之后,又多了16字节的对象头(8字节MarkWord+8字节Klass指针=16字节),然后一个8字节的next字段和4字节的int型的hash字段(8字节next指针+4字节hash字段+4字节填充=16字节),为了对齐,还必须添加4字节的空白填充,最后还有HashMap中对这个Entry的8字节的引用,这样增加两个长整型数字,实际耗费的内存为(Long(24byte)×2)+Entry(32byte)+HashMapRef(8byte)=88byte,空间效率为有效数据除以全部内存空间,即16字节/88字节=18%。
——《深入理解Java虚拟机》5.2.6
以下是刚上传的excel中dump出的堆内存对象,其占用的内存达到了128mb,而上传的excel实际只有17.11mb。

空间效率17.1mb/128mb≈13.4%
2.2 如何解决此问题
暂且不讨论上述流程是否合理,解决办法一般可以分为两类,一类是治本,即不把该对象放入jvm内存中,转而存入缓存中,不在内存中则大对象问题自然迎刃而解。另一类是治标,即缩小该大内存对象,在日常使用场景下使其一般不会触发频繁的full gc问题。

相关文章
|
Android开发
mac下配置adb环境变量
在终端中输入adb命令时,会提示 command not found ,这是是因为mac电脑下没有配置Android环境变量或者环境变量配置错误。
|
8月前
|
JavaScript 前端开发 算法
React框架
React是一个用于构建用户界面的JavaScript库,专注于视图层,采用虚拟DOM和Diff算法实现高效渲染。支持组件化开发、服务端渲染、状态管理,易于测试与集成,通过生命周期、setState机制及高阶组件等特性提升开发效率与性能。
|
5月前
|
弹性计算 人工智能 Linux
阿里云服务器使用指南:2026新手入门指南,一篇文章全搞定
2026阿里云新手指南:详解轻量应用服务器与ECS区别,手把手教你三步购买(含¥38/年秒杀技巧)、网页/终端远程登录、WordPress/OpenClaw一键部署,并附防火墙配置、密码修改等安全设置及常见问题排查方案。
795 1
|
4月前
|
Ubuntu API C++
OpenClaw 阿里云、本地搭建,+Windows WSL+本地源码安装与自定义修改保姆级教程
2026年OpenClaw作为灵活的AI Agent平台,不仅支持阿里云规模化部署与本地轻量运行,还能通过Windows WSL(Windows Subsystem for Linux)实现源码级安装与自定义修改——这种方式特别适合开发者学习底层逻辑、按需拓展功能,而受OpenClaw启发的超轻量级版本nanobot(仅4000行代码),更是入门源码学习的最佳选择。
676 0
|
人工智能 自然语言处理 测试技术
Goedel-Prover:专为自动化数学问题的形式证明生成而设计的 LLM,快速解决形式化数学问题
Goedel-Prover 是一款由普林斯顿大学和清华大学等机构联合推出的开源模型,专注于自动化数学问题的形式证明生成。它通过将自然语言数学问题翻译成形式语言(如 Lean 4),显著提升了数学问题的证明效率。
864 4
Goedel-Prover:专为自动化数学问题的形式证明生成而设计的 LLM,快速解决形式化数学问题
|
SQL 关系型数据库 数据库
postgresql报:ERROR: column “i“ of relation “test“ does not exist LINE 1: UPDATE怎么解决?
解决“ERROR: column "i" of relation "test" does not exist”错误的关键在于核实列名的准确性,修正更新语句,确保列名的引用正确无误,并考虑到任何可能影响列名引用的表别名、大小写、特殊字符或动态SQL生成等因素。通过上述步骤,你应该能有效定位并解决问题,保证SQL语句的正确执行。
1557 0
|
网络安全 Windows
查看SSH配置文件
查看SSH配置文件
2440 1
|
存储 算法 C++
【C++核心】结构体、共用体详解
这篇文章详细讲解了C++中结构体和共用体的概念、定义、使用场景和案例,包括结构体的创建、数组、指针、嵌套、函数参数传递,以及共用体的特点和应用实例。
453 4
|
Python
基本技术指标 Python 实现(2)
基本技术指标 Python 实现
589 1
|
Linux
linux cat查看文件使用grep实现多条件多场景过滤
linux cat查看文件使用grep实现多条件多场景过滤
1749 0