FullGC排查,居然是它!

前言

我是[提前退休的java猿](https://juejin.cn/user/465848660928872 "https://juejin.cn/user/465848660928872"),一名7年java开发经验的开发组长,分享工作中的各种问题!(抖音、公众号同号)

🔈 今天这篇文章一定值得你看 转载前公司CTO发的文章:FullGC排查,居然是它

项目上有 Full GC 告警,每个节点平均每天有一到两次Full GC 告警。出现Full GC时,应用是处于 Stop The World 状态,对于高可用要求的应用来说,这是一个很大的稳定性风险。就在告警出现的这几天,上游业务方也联系到我,说他们近期调用我们的接口超时,通过监控来看,调用超时的时间正好也就是Full GC出现的时间。虽然我其实并没有排查过Full GC问题,不过我还是想试一下。

问题分析排查

通过查看Full GC日志,发现Full GC是由于达到Metaspace Threadhold 触发,GC耗时在2-4S。

js 复制代码
2025-09-27T14:33:02.210+0800: 312596.342:  
[FullGC(MetadataGCThreshold) 3169M->553M(6144M), 3.5247678secs]  
[Eden: 1258.0M(3594.0M)->0.0B(3686.0M) Survivors: 22528.0K->0.0B Heap: 3169.3M(6144.0M)->554.0M(6144.0M)], [Metaspace: 752742K->303689K(1511424K)]  
[Times: user=4.20 sys=0.43, real=3.52 secs]

既然是元空间,那我们通过应用监控查看当时的元空间情况,当Metaspace 达到700多MB时,就会触发Full GC, GC后元空间降到300多MB,释放了400M内存。

元空间主要是存放 class 信息,看一下类数量的监控,在FullGC时,类数量达到15万,GC后降低到4万,释放了11万个class。

正常情况下 class 是相对稳定的,通常是由于动态代理、序列化、脚本引擎这类场景会动态生成类。

通过类监控的增长曲线发现,工作时间的类增长曲线明显比非工作时间的类增长曲线更陡峭,说明大概率是工作时间调用的某个接口的内部逻辑导致。

通过接口调用数量监控,判断可能和其中三个接口有关。于是从接口调用入口出发捋代码逻辑,找到一段调用 Aviator 框架的代码。

java 复制代码
AviatorEvaluatorInstance instance = ExpressContext.getInstance();  
ExpressionLexer lexer = new ExpressionLexer(instance, expression);  
CodeGenerator codeGenerator = new OptimizeCodeGenerator(instance, null, CLASS_LOADER, null);
ExpressionParser parser = new ExpressionParser(instance, lexer, codeGenerator);
parser.parse();

这段代码看着就可疑,又是CodeGenerator,又用到ClassLoader,这些类名仿佛就是在和我说,别找了,就是我。

于是写了一段单元测试代码,循环调用,jvm启动参数上加上

ruby 复制代码
-XX:+TraceClassLoading -XX:+TraceClassUnloading

运行后发现有大量类的生成和卸载,完全满足触发 Metapsace Threshold 的条件。此时此刻,我已经有80%的把握就是它的问题了。不过熟悉我的人都知道,我不仅是一个解决问题能力还不错的人,同时我还是一个保守的人, 没有十足的把握我是不会下结论,我可不想在新团队砸了自己招牌。

现在一切猜想都很符合逻辑,现在就差最后的证明,那就是类统计信息。在这不像我以前的时候,各种环境我都是超管权限,想怎么弄就怎么弄。我无法登录到容器内部用jcmd或者 jmap histo命令去统计类,容器平台上提供了堆dump的功能,但是堆dump是一个非常重量级的操作,为了不影响业务,我只能等。

在国庆期间,大家都休假了,我做了一个决定,我偷偷的登录到云平台,关闭其中一个节点的流量,导出堆内存,导入MAT进行分析。此时我心情是平静的,我知道我对了,我只是要让MAT告诉我。我本将心向明月,然明月何曾是两乡,MAT没有辜负我,它小声的和我说:你对了。

解决方案

这些类就是Aviator框架生成的,类名后缀是顺序递增,总共生成过100多万个class。解决方案通过增加表达式的LRU缓存解决这个问题,上线后类数量非常平稳,Full GC也消失了。加LRU缓存也只是临时方案,最好是Aviator官方能支持我这种场景,给他们提了issue,不过大概率他们是不会同意的,毕竟它也只是一个个人项目,靠爱发电不太靠得住。你看我,想啥时候发就啥时候发。

github.com/killme2008/...

相关推荐
葫芦和十三6 小时前
图解 MongoDB 04|索引模型:每建一个索引,就是在 B+-tree 森林里多栽一棵
后端·mongodb·agent
用户47949283569157 小时前
claude Fable用不了?把Gpt 5.5pro接到你的claude code里
前端·后端
GetcharZp9 小时前
告别 Nginx 复杂配置!这款带 Web 面板的万能代理神器,让端口转发变得如此简单
后端
IT_陈寒11 小时前
React的useState居然还有这种坑?我差点删库跑路
前端·人工智能·后端
nanxun88612 小时前
记一次诡异的 Docker 容器"串包"故障排查
java
Pedantic12 小时前
SwiftUI 手势笔记
前端·后端
金銀銅鐵12 小时前
[Python] 从《千字文》中随机挑选汉字
后端·python
用户15630681035115 小时前
Day01 | Java 基础(Java SE)
java
飘尘15 小时前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈