糟糕,生产环境频繁Full GC,怎么办?
前言
我们在面试时,经常会被面试官问到:线上服务频繁Full GC该如何优化?
今天这篇文章跟大家一起聊聊这个话题,希望对你会有所帮助。
1. 什么是Full GC?
当老年代空间不足时,JVM会触发Stop-The-World的全局回收(Full GC),暂停所有应用线程。
致命危害(生产环境实测):
| 暂停时间 | 业务影响 |
|---|---|
| 1秒 | 支付超时率上升5% |
| 3秒 | 数据库连接池耗尽 |
| 10秒 | 服务被注册中心摘除 |
对象的晋升之路流程图:

关键代码:年龄计数器
// HotSpot虚拟机源码片段(objectMonitor.cpp)
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock) {
if (obj->age() >= MaxTenuringThreshold) { // 年龄阈值检查
promote_to_old_gen(obj); // 晋升老年代
}
}
2.如何排查定位问题?
2.1 实时监控:GC健康度速诊
jstat -gcutil <pid> 1000 # 每秒输出GC数据
关键指标解读:
- OU:老年代使用率 > 90% = 危险区
- FGCT:Full GC总耗时 > 应用运行时间10% = 严重问题
2.2. 堆内存转储:揪出内存黑洞
jmap -dump:live,format=b,file=heap.bin <pid> # 生产环境慎用live
2.3 MAT深度分析:解剖内存泄漏

3.优化方案
方案1:对象池化——大对象的救赎
场景:高频创建10MB的文件缓存
// 反例:每次请求创建新对象
public void processRequest(Request req) {
byte[] buffer = new byte[10 * 1024 * 1024]; // 10MB
// ...处理逻辑
}
// 优化:对象池复用
private static final ObjectPool<byte[]> pool = new GenericObjectPool<>(
new BasePooledObjectFactory<byte[]>() {
@Override
public byte[] create() {
return new byte[10 * 1024 * 1024];
}
}
);
public void processRequest(Request req) throws Exception {
byte[] buffer = pool.borrowObject();
try {
// ...处理逻辑
} finally {
pool.returnObject(buffer);
}
}
效果:老年代分配速率下降85%
方案2:手动控制晋升
问题:Survivor区过小导致对象提前晋升
优化参数:
-XX:TargetSurvivorRatio=60 # Survivor区使用阈值
-XX:MaxTenuringThreshold=15 # 最大晋升年龄
-XX:+NeverTenure # 若Survivor足够,永不晋升(慎用!)
晋升原理:

方案3:合理分配堆空间
经典误区:
-Xmx4g -Xms4g # 错误!未配置新生代
优化公式:
新生代大小 = 总堆 * 3/8
Eden:Survivor = 8:1:1
正确配置:
-Xmx8g -Xms8g
-Xmn3g # 新生代3G (8*3/8≈3)
-XX:SurvivorRatio=8 # Eden:Survivor=8:1:1
方案4:卸载无用类
场景:热部署频繁的应用(如JRebel)
诊断命令:
jcmd <pid> VM.class_stats # JDK8+
jcmd <pid> GC.class_stats # JDK11+
根治代码:
// 自定义类加载器必须实现close()
public class HotSwapClassLoader extends URLClassLoader {
@Override
public void close() throws IOException {
// 1. 停止新请求
// 2. 卸载所有类
// 3. 关闭资源
}
}
方案5:颠覆传统的ZGC
传统GC痛点:
- CMS:内存碎片问题
- G1:Mixed GC不可控
ZGC迁移步骤:
- 升级JDK至17+
- 添加参数:
-XX:+UseZGC
-XX:ZAllocationSpikeTolerance=5.0 # 容忍内存分配速率波动
-Xmx16g -Xlog:gc*:file=gc.log
效果对比:
| 指标 | CMS | ZGC |
|---|---|---|
| Full GC次数 | 15次/天 | 0次/天 |
| 最大暂停 | 2.8秒 | 1.2毫秒 |
方案6:堆外内存治理
现象:堆内存正常,但Full GC频繁
根源:DirectByteBuffer的清理依赖Full GC
防御方案:
// 方案1:限制堆外内存
-XX:MaxDirectMemorySize=512m
// 方案2:主动调用Cleaner
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
Cleaner cleaner = ((DirectBuffer) buffer).cleaner();
if (cleaner != null) cleaner.clean();
// 方案3:Netty的内存管理
PooledByteBufAllocator allocator = new PooledByteBufAllocator(true);
ByteBuf buffer = allocator.directBuffer(1024);
// ...使用后必须release!
buffer.release();
4.实战案例
背景:某支付系统日均交易10亿
症状:
- 每分钟5次Full GC,暂停4.2秒
- 99线响应时间从50ms飙升至3秒
排查过程:
jstat显示老年代10秒内从60%→99%- MAT分析发现
ConcurrentHashMap$Node[]占78%内存 - 溯源代码找到缓存黑洞:
// 问题代码:永不失效的缓存
Map<String, Transaction> cache = new ConcurrentHashMap<>();
public void cacheTransaction(Transaction tx) {
cache.put(tx.getId(), tx); // Key冲突时旧对象未移除!
}
解决方案:
- 改用Caffeine缓存:
Cache<String, Transaction> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
- 添加ZGC参数
- 重写线程池任务队列:
// 用有界队列替代LinkedBlockingQueue
new ThreadPoolExecutor(..., new ArrayBlockingQueue<>(1000));
效果:
- Full GC降为0
- 99线回落至68ms
总结
- 监控三件套:
jstat -gcutil <pid> 1000 # 实时监控
-Xlog:gc*:file=gc.log # GC日志
Prometheus + Grafana # 可视化大盘
- 参数黄金法则:

- 代码军规:
- 大对象必须池化
- 缓存必须设置上限
- 线程池必须用有界队列
- GC算法选择:
场景 推荐算法 堆<8G Parallel 8G~32G G1 关键业务系统 ZGC
Full GC不是优化出来的,是设计出来的!
永远在架构设计阶段预留30%内存缓冲空间,比任何调参技巧都重要。
附录:急救工具箱
| 工具 | 命令 | 适用场景 |
|---|---|---|
| jcmd | jcmd <pid> GC.run |
主动触发Full GC |
| Arthas | vmtool --action getHeap |
内存快照 |
| btrace | 监控DirectByteBuffer分配 | 堆外内存泄漏 |
| PerfMa | 在线分析GC日志 | 自动化诊断 |
最后说一句(求关注,别白嫖我)
如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下我的同名公众号:苏三说技术,您的支持是我坚持写作最大的动力。
求一键三连:点赞、转发、在看。
关注公众号:【苏三说技术】,在公众号中回复:进大厂,可以免费获取我最近整理的10万字的面试宝典,好多小伙伴靠这个宝典拿到了多家大厂的offer。
本文收录于我的技术网站:http://www.susan.net.cn
糟糕,生产环境频繁Full GC,怎么办?的更多相关文章
- 生产环境频繁内存溢出,原来就是因为这个“String类”
摘要:如果在程序中创建了比较大的对象,并且我们基于这个大对象生成了一些其他的信息,此时,一定要释放和这个大对象的引用关系,否则,就会埋下内存溢出的隐患. 本文分享自华为云社区<[高并发]你敢信? ...
- Dubbo Mesh 在闲鱼生产环境中的落地实践
本文作者至简曾在 2018 QCon 上海站以<Service Mesh 的本质.价值和应用探索>为题做了一次分享,其中谈到了 Dubbo Mesh 的整体发展思路是“借力开源.反哺开源” ...
- 生产环境如何快速跟踪、分析、定位问题-Java
我相信做技术的都会遇到过这样的问题,生产环境服务遇到宕机的情况下如何去分析问题?比如说JVM内存爆掉.CPU持续高位运行.线程被夯住或线程deadlocks,面对这样的问题,如何在生产环境第一时间跟踪 ...
- 【生产环境】Tomcat运行一段时间后访问变慢分析历程
环境运行一天或者几天,网站访问就很卡,手机端app访问页面出现白屏.Tomcat运行一段时间后访问变慢,但是cpu,内存都正常.日志也是发现不了啥.... 问题的原先分析 1.环境配置(cpu,内存, ...
- nginx 和 tomcat 生产环境配置 建议和方法
参考 以下内容: http://blog.csdn.net/lifetragedy/article/details/7708724 一. nginx参数调优 worker_processes 3; ...
- 阿里开源 Dragonwell JDK 重磅发布 GA 版本:生产环境可用
今年 3 月份,阿里巴巴重磅开源 OpenJDK 长期支持版本 Alibaba Dragonwell的消息,在很长一段时间内都是开发者的讨论焦点,该项目在 Github 上的 Star 数迅速突破 1 ...
- 关于生产环境改用G1垃圾收集器的思考
背景 由于我们的业务量非常大,响应延迟要求高.目前沿用的老的ParNew+CMS已经不能支撑业务的需求.平均一台机器在1个月内有1次秒级别的stop the world.对系统来说是个巨大的隐患.所以 ...
- 【Java面试】生产环境服务器变慢,如何诊断处理?
"生产环境服务器变慢?如何诊断处理" 这是最近一些工作5年以上的粉丝反馈给我的问题,他们去一线大厂面试,都被问到了这一类的问题. 今天给大家分享一下,面试过程中遇到这个问题,我们应 ...
- linux iptables常用命令之配置生产环境iptables及优化
在了解iptables的详细原理之前,我们先来看下如何使用iptables,以终为始,有可能会让你对iptables了解更深 所以接下来我们以配置一个生产环境下的iptables为例来讲讲它的常用命令 ...
- 理解Docker(6):若干企业生产环境中的容器网络方案
本系列文章将介绍 Docker的相关知识: (1)Docker 安装及基本用法 (2)Docker 镜像 (3)Docker 容器的隔离性 - 使用 Linux namespace 隔离容器的运行环境 ...
随机推荐
- C#实现MCP Client 与 LLM 连接,抓取网页内容功能!
前面的课程,我们已经用C#实现了,自己的MCP Client. 下面我们一起来实现,MCP Client与LLM 对接. 一.添加依赖库 目前来说,绝大部分的大模型的API,都是遵循OpenAI的接口 ...
- Web前端入门第 62 问:JavaScript 循环结构注意事项
HELLO,这里是大熊的前端开发笔记. 循环作为 算法与数据结构 中的基石,JS 与其他编程语言一样,都提供了多种循环结构用于处理数据. for 循环 事物的开端往往都是从最常用的开始,循环结构咱们从 ...
- flutter3-winseek客户端AI实例|Flutter3.32+DeepSeek流式ai对话模板Exe
原创首发flutter3+deepseek+window_manager客户端Ai流式打字Flutter-WinSeek. flutter3-winseek-chat:基于flutter3.32+da ...
- 「Note」数据结构方向 - 可持久化数据结构
1. 可持久化线段树 1.1. 简介 可持久化线段树一般用于解决区间第 \(k\) 小值的询问. 首先考虑简化过的问题,区间 \(\left[1,r\right]\) 的第 \(k\) 小值. 考虑用 ...
- Pod的调度
在默认情况下,一个Pod在哪个Node节点上运行,是由Scheduler组件采用相应的算法计算出来的,这个过程是不受人工控制的.但是在实际使用中,这并不满足需求,因为很多情况下,我们想控制某些Pod到 ...
- DeepSeek LLM
作者前言: DeepSeek系列现在非常火,笔者决定主要梳理DeepSeekzui最重要的四代版本: DeepSeek-LLM; DeepSeekMath; DeepSeek-V2; DeepSeek ...
- SAP发布简易REST 三:API平台之接口文档
为了完善API接口,增强友好性,做了一点小文档展示. 新建配置表:(用来做接口参数配置展示使用,不在程序中应用) 因为需要给每个接口一个对应的名字,所以在原来的API控制表中增加一个文本字段. 这里加 ...
- 探索大模型:袋鼠云在 Text To SQL 上的实践与优化
Text To SQL 指的是将自然语言转化为能够在关系型数据库中执行的结构化查询语言(简称 SQL).近年来,伴随人工智能大模型技术的不断进步,Text To SQL 任务的成功率显著提升,这得益于 ...
- .Net 9.0环境下WebApi发布到IIS
一.在Visual Studio里发布 右键点击WebApi工程,点击发布按钮,如下图所示. 点击[发布]按钮后,系统弹出发布对话框,如下图所示. 选择文件夹,点击[下一步]. 在该界面选择发布输出的 ...
- Oracle中字符型级处理方法
字符型简介 固定长度字符串-char(n) n代表字符串的长度,当实际长度不足时,利用空格在右端补齐,n的最大值不能大于2000.所以只要是固定长度的字符串,他的length(值)的长度总为n var ...