找回密码
 立即注册
首页 业界区 业界 糟糕,生产环境频繁Full GC,怎么办?

糟糕,生产环境频繁Full GC,怎么办?

敕码 昨天 14:59
前言

我们在面试时,经常会被面试官问到:线上服务频繁Full GC该如何优化?
今天这篇文章跟大家一起聊聊这个话题,希望对你会有所帮助。
1. 什么是Full GC?

当老年代空间不足时,JVM会触发Stop-The-World的全局回收(Full GC),暂停所有应用线程。
致命危害(生产环境实测):
暂停时间业务影响1秒支付超时率上升5%3秒数据库连接池耗尽10秒服务被注册中心摘除对象的晋升之路流程图:
1.png

关键代码:年龄计数器
  1. // HotSpot虚拟机源码片段(objectMonitor.cpp)
  2. void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock) {
  3.   if (obj->age() >= MaxTenuringThreshold) { // 年龄阈值检查
  4.     promote_to_old_gen(obj); // 晋升老年代
  5.   }
  6. }
复制代码
2.如何排查定位问题?

2.1 实时监控:GC健康度速诊
  1. jstat -gcutil <pid> 1000  # 每秒输出GC数据
复制代码
关键指标解读

  • OU:老年代使用率 > 90% = 危险区
  • FGCT:Full GC总耗时 > 应用运行时间10% = 严重问题
2.2. 堆内存转储:揪出内存黑洞
  1. jmap -dump:live,format=b,file=heap.bin <pid>  # 生产环境慎用live
复制代码
2.3 MAT深度分析:解剖内存泄漏

2.png

3.优化方案

方案1:对象池化——大对象的救赎

场景:高频创建10MB的文件缓存
  1. // 反例:每次请求创建新对象
  2. public void processRequest(Request req) {
  3.     byte[] buffer = new byte[10 * 1024 * 1024]; // 10MB
  4.     // ...处理逻辑
  5. }
  6. // 优化:对象池复用
  7. private static final ObjectPool<byte[]> pool = new GenericObjectPool<>(
  8.     new BasePooledObjectFactory<byte[]>() {
  9.         @Override
  10.         public byte[] create() {
  11.             return new byte[10 * 1024 * 1024];
  12.         }
  13.     }
  14. );
  15. public void processRequest(Request req) throws Exception {
  16.     byte[] buffer = pool.borrowObject();
  17.     try {
  18.         // ...处理逻辑
  19.     } finally {
  20.         pool.returnObject(buffer);
  21.     }
  22. }
复制代码
效果:老年代分配速率下降85%
方案2:手动控制晋升

问题:Survivor区过小导致对象提前晋升
优化参数
  1. -XX:TargetSurvivorRatio=60  # Survivor区使用阈值
  2. -XX:MaxTenuringThreshold=15 # 最大晋升年龄
  3. -XX:+NeverTenure            # 若Survivor足够,永不晋升(慎用!)
复制代码
晋升原理
3.png

方案3:合理分配堆空间

经典误区
  1. -Xmx4g -Xms4g  # 错误!未配置新生代
复制代码
优化公式
  1. 新生代大小 = 总堆 * 3/8  
  2. Eden:Survivor = 8:1:1  
复制代码
正确配置
  1. -Xmx8g -Xms8g
  2. -Xmn3g  # 新生代3G (8*3/8≈3)
  3. -XX:SurvivorRatio=8  # Eden:Survivor=8:1:1
复制代码
方案4:卸载无用类

场景:热部署频繁的应用(如JRebel)
诊断命令
  1. jcmd <pid> VM.class_stats  # JDK8+
  2. jcmd <pid> GC.class_stats  # JDK11+
复制代码
根治代码
  1. // 自定义类加载器必须实现close()
  2. public class HotSwapClassLoader extends URLClassLoader {
  3.     @Override
  4.     public void close() throws IOException {
  5.         // 1. 停止新请求
  6.         // 2. 卸载所有类
  7.         // 3. 关闭资源
  8.     }
  9. }
复制代码
方案5:颠覆传统的ZGC

传统GC痛点

  • CMS:内存碎片问题
  • G1:Mixed GC不可控
ZGC迁移步骤

  • 升级JDK至17+
  • 添加参数:
  1. -XX:+UseZGC
  2. -XX:ZAllocationSpikeTolerance=5.0  # 容忍内存分配速率波动
  3. -Xmx16g -Xlog:gc*:file=gc.log
复制代码
效果对比
指标CMSZGCFull GC次数15次/天0次/天最大暂停2.8秒1.2毫秒方案6:堆外内存治理

现象:堆内存正常,但Full GC频繁
根源:DirectByteBuffer的清理依赖Full GC
防御方案
  1. // 方案1:限制堆外内存
  2. -XX:MaxDirectMemorySize=512m
  3. // 方案2:主动调用Cleaner
  4. ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
  5. Cleaner cleaner = ((DirectBuffer) buffer).cleaner();
  6. if (cleaner != null) cleaner.clean();
  7. // 方案3:Netty的内存管理
  8. PooledByteBufAllocator allocator = new PooledByteBufAllocator(true);
  9. ByteBuf buffer = allocator.directBuffer(1024);
  10. // ...使用后必须release!
  11. buffer.release();
复制代码
4.实战案例

背景:某支付系统日均交易10亿
症状

  • 每分钟5次Full GC,暂停4.2秒
  • 99线响应时间从50ms飙升至3秒
排查过程

  • jstat显示老年代10秒内从60%→99%
  • MAT分析发现ConcurrentHashMap$Node[]占78%内存
  • 溯源代码找到缓存黑洞:
  1. // 问题代码:永不失效的缓存
  2. Map<String, Transaction> cache = new ConcurrentHashMap<>();
  3. public void cacheTransaction(Transaction tx) {
  4.     cache.put(tx.getId(), tx); // Key冲突时旧对象未移除!
  5. }
复制代码
解决方案

  • 改用Caffeine缓存:
  1. Cache<String, Transaction> cache = Caffeine.newBuilder()
  2.     .maximumSize(10_000)
  3.     .expireAfterWrite(5, TimeUnit.MINUTES)
  4.     .build();
复制代码

  • 添加ZGC参数
  • 重写线程池任务队列:
  1. // 用有界队列替代LinkedBlockingQueue
  2. new ThreadPoolExecutor(..., new ArrayBlockingQueue<>(1000));
复制代码
效果

  • Full GC降为0
  • 99线回落至68ms
总结


  • 监控三件套
  1. jstat -gcutil <pid> 1000  # 实时监控
  2. -Xlog:gc*:file=gc.log     # GC日志
  3. Prometheus + Grafana      # 可视化大盘
复制代码

  • 参数黄金法则
4.png

<ol start="3">代码军规

  • 大对象必须池化
  • 缓存必须设置上限
  • 线程池必须用有界队列
GC算法选择:[table][tr]场景推荐算法[/tr][tr][td]堆
您需要登录后才可以回帖 登录 | 立即注册