本文目录导读:

GC(垃圾回收)频繁通常表现为CPU飙升、系统卡顿、接口响应变慢、出现STW(Stop-The-World)暂停,优化解决的核心思路是:减少对象创建速度、降低对象存活时间、选择合适的GC算法和堆内存配置。
以下是针对不同场景的系统性优化步骤和排查方法:
第一阶段:诊断与定位
在动手优化前,必须先明确问题根源,否则可能适得其反。
-
确认GC频率和类型
- 使用
jstat -gcutil <pid> <间隔毫秒>查看GC情况。 - 关键指标:
YGC(Young GC次数)、YGCT(Young GC总耗时)、FGC(Full GC次数)、FGCT、S0/S1(幸存区使用率)。 - 告警信号:Full GC频繁(如几分钟一次甚至更快)、Young GC间隔极短(如几秒一次)、GC时间占比过高(吞吐量下降)。
- 使用
-
获取GC日志(最重要的第一手资料)
- 添加JVM参数:
-Xloggc:/path/to/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps。 - 使用工具分析:
gceasy.io(在线)、GCViewer、GCeasy。 - 关注点:GC前后内存变化、平均暂停时间、晋升到老年代的对象大小。
- 添加JVM参数:
-
分析堆内存Dump(看具体是什么对象)
- 当发生 Full GC 时,用
jmap -dump:live,format=b,file=heap.hprof <pid>抓取堆转储。 - 使用
Eclipse MAT或JProfiler分析。 - 关键排查点:
- 占用内存最多的对象类型(如
byte[]、HashMap$Node、Thread)。 - GC Roots 路径:看对象为什么没被回收。
- 大对象:是否一次性加载了大量数据到内存。
- 占用内存最多的对象类型(如
- 当发生 Full GC 时,用
第二阶段:代码与业务层面的优化(最根本、最有效)
大多数GC问题根源在于代码写了太多生命周期短的对象或持有无用对象引用。
-
减少对象创建
- 循环内避免创建对象:例如在循环里
new StringBuilder(),应提到循环外。 - 使用StringBuilder/StringBuffer避免字符串拼接: 操作会创建多个中间String对象。
- 避免自动装箱拆箱:如
Integer与int频繁转换,尤其在循环和集合中。 - 使用对象池:对于创建代价高、重用频率高的对象(如数据库连接、线程、大对象),考虑对象池(如
commons-pool2)。
- 循环内避免创建对象:例如在循环里
-
及时释放引用(防止内存泄漏)
- 用完即置空:静态集合(
Map、List)中向容器添加对象后,如果容器生命周期太长,对象永远不会被回收,最终导致老年代爆满。 - 监听器/回调:注册了监听器或观察者模式,用完后必须显式移除。
- ThreadLocal:使用后必须调用
remove(),否则线程池中线程复用会导致内存泄漏。 - 流/连接关闭:
InputStream、Connection、Session等需在finally块中关闭,或使用 try-with-resources。
- 用完即置空:静态集合(
-
优化数据结构
- 使用基本类型数组:
int[]比List<Integer>省内存,且GC压力小。 - 减少大对象的数量:例如一次查询DB返回所有数据,应分页(
LIMIT)或流式查询。 - 选择合适的集合初始容量:
HashMap、ArrayList指定初始容量,避免频繁扩容(扩容会复制数组,产生大量对象)。
- 使用基本类型数组:
第三阶段:JVM参数配置优化(在代码优化基础上进行)
调整参数不能解决代码缺陷,但可以给系统争取时间。
-
调整堆大小
-Xms和-Xmx通常设为相同值(防止运行时动态调整大小导致性能波动)。- 对于Young GC频繁,增大堆空间可以拉开GC间隔,但会增加每次GC的暂停时间(取决于垃圾收集器)。
- 经验值:
-Xms4g -Xmx4g(具体取决于应用内存需求)。
-
调整新生代(Young Generation)比例
-XX:NewRatio:老年代/新生代比例,默认2(新生代1/3),调大新生代(如设为1)可减少Young GC频率。-Xmn:直接指定新生代大小(通常设为堆的1/3到1/2)。-XX:SurvivorRatio:Eden/Survivor比例,默认8,如果幸存区太小,对象会过早晋升到老年代,导致FullGC。
-
调整大对象直接进入老年代的阈值
-XX:PretenureSizeThreshold:大于此值的对象直接进入老年代(避免在Eden和Survivor之间反复拷贝),默认0(全部先Eden)。- 谨慎使用:若设置过小,大对象直接进老年代会加速FullGC。
-
选择合适的GC收集器(针对不同场景)
应用场景 推荐收集器 特点 响应时间优先(如Web、API) G1 (Garbage First) 可预测暂停时间,并发回收,适合大堆(>4G)。 吞吐量优先(如离线计算、批处理) Parallel Scavenge + Parallel Old 追求最大化CPU利用率,暂停时间较长。 低延迟、超小堆(如实时系统) ZGC 或 Shenandoah 暂停时间 < 10ms,但CPU开销较高。 - G1调优关键:
-XX:MaxGCPauseMillis=200:设置期望的最大停顿时间(毫秒),默认200ms。-XX:G1HeapRegionSize=32m:分区大小(1MB~32MB)。-XX:G1NewSizePercent:新生代初始占比。
- G1调优关键:
第四阶段:特定场景的专项优化
- Full GC频繁但老年代使用率不高:可能是元空间(Metaspace) 满了或CMS(Concurrent Mark Sweep)回收失败,检查
-XX:MaxMetaspaceSize是否过小,或G1的-XX:G1MixedGCLiveThresholdPercent配置。 - Young GC后大量对象晋升:说明Eden区太小或Survivor区太小(
-XX:TargetSurvivorRatio默认50%),调大新生代或Survivor空间。 - 请求量大、服务器重启时(流量高峰):预先增大新生代(
-Xmn),并开启-XX:+UseTLAB(线程本地分配缓冲),减少线程竞争。 - 使用堆外内存(NIO/DirectByteBuffer):注意
-XX:MaxDirectMemorySize,若堆外内存泄漏会导致OOM和频繁GC。
优化思路优先级
- 代码清理(最优先):修内存泄漏、减少对象创建。
- 业务分流(次优先):异步化、削峰、分页。
- JVM参数调整(辅助):给应用合适的“作案空间”。
- 监控与告警(持续):设置GC暂停时间阈值(如 > 500ms 告警),持续观察。
最终建议:如果GC问题一直无法解决,优先拿一份GC日志和堆Dump,用工具可视化分析对象分布,通常能直接定位到导致问题的代码行。