JVM频繁Full GC的情况及应对策略

本文介绍了触发JVM Full GC的各种原因,包括System.gc()的调用、老年代空间不足、永生区空间不足等,并针对每种情况提出了相应的优化策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


堆内存划分为 Eden、Survivor 和 Tenured/Old 空间,如下图所示:

从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC,对老年代GC称为Major GC,而Full GC是对整个堆来说的,在最近几个版本的JDK里默认包括了对永生带即方法区的回收(JDK8中无永生带了),出现Full GC的时候经常伴随至少一次的Minor GC,但非绝对的。Major GC的速度一般会比Minor GC慢10倍以上。下边看看有那种情况触发JVM进行Full GC及应对策略。

 

1、System.gc()方法的调用

 

此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。


2、老年代代空间不足


老年代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误:
java.lang.OutOfMemoryError: Java heap space 
为避免以上两种状况引起的Full GC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。

3、永生区空间不足


JVM规范中运行时数据区域中的方法区,在HotSpot虚拟机中又被习惯称为永生代或者永生区,Permanet Generation中存放的为一些class的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下也会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:
java.lang.OutOfMemoryError: PermGen space 
为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。


4、CMS GC时出现promotion failed和concurrent mode failure


对于采用CMS进行老年代GC的程序而言,尤其要注意GC日志中是否有promotion failed和concurrent mode failure两种状况,当这两种状况出现时可能

会触发Full GC。
promotion failed是在进行Minor GC时,survivor space放不下、对象只能放入老年代,而此时老年代也放不下造成的;concurrent mode failure是在

执行CMS GC的过程中同时有对象要放入老年代,而此时老年代空间不足造成的(有时候“空间不足”是CMS GC时当前的浮动垃圾过多导致暂时性的空间不足触发Full GC)。
对措施为:增大survivor space、老年代空间或调低触发并发GC的比率,但在JDK 5.0+、6.0+的版本中有可能会由于JDK的bug29导致CMS在remark完毕

后很久才触发sweeping动作。对于这种状况,可通过设置-XX: CMSMaxAbortablePrecleanTime=5(单位为ms)来避免。


5、统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间


这是一个较为复杂的触发情况,Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行Minor GC时,做了一个判断,如果之

前统计所得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间,那么就直接触发Full GC。
例如程序第一次触发Minor GC后,有6MB的对象晋升到旧生代,那么当下一次Minor GC发生时,首先检查旧生代的剩余空间是否大于6MB,如果小于6MB,

则执行Full GC。
当新生代采用PS GC时,方式稍有不同,PS GC是在Minor GC后也会检查,例如上面的例子中第一次Minor GC后,PS GC会检查此时旧生代的剩余空间是否

大于6MB,如小于,则触发对旧生代的回收。
除了以上4种状况外,对于使用RMI来进行RPC或管理的Sun JDK应用而言,默认情况下会一小时执行一次Full GC。可通过在启动时通过- java -

Dsun.rmi.dgc.client.gcInterval=3600000来设置Full GC执行的间隔时间或通过-XX:+ DisableExplicitGC来禁止RMI调用System.gc。

 

6、堆中分配很大的对象

 

所谓大对象,是指需要大量连续内存空间的java对象,例如很长的数组,此种对象会直接进入老年代,而老年代虽然有很大的剩余空间,但是无法找到足够大的连续空间来分配给当前对象,此种情况就会触发JVM进行Full GC。

为了解决这个问题,CMS垃圾收集器提供了一个可配置的参数,即-XX:+UseCMSCompactAtFullCollection开关参数,用于在“享受”完Full GC服务之后额外免费赠送一个碎片整理的过程,内存整理的过程无法并发的,空间碎片问题没有了,但提顿时间不得不变长了,JVM设计者们还提供了另外一个参数 -XX:CMSFullGCsBeforeCompaction,这个参数用于设置在执行多少次不压缩的Full GC后,跟着来一次带压缩的。

<think>我们正在解决JVM频繁Full GC的问题。根据引用内容,频繁Full GC通常与内存设置、GC策略、内存泄漏、代码优化等有关。我们将从原因分析到解决方案逐步展开。 引用[1]提到:频繁Full GC与内存设置不当、垃圾回收器配置不合适、内存泄漏或代码优化不足有关。 引用[2]给出了一些JVM参数调整建议,并提到使用G1收集器可能有助于减少Full GC。 引用[3]指出根本原因可能包括:请求速率过高、Minor GC频繁导致对象提前晋升、大量临时对象、线程池配置不合理等。 引用[4]则介绍了一个关于SoftReference的参数。 因此,我们将从以下几个方面进行分析和优化: 1. 内存设置与GC策略调整 2. 内存泄漏排查 3. 代码优化 4. 其他配置优化 下面详细展开:</think>### JVM 频繁 Full GC 的原因分析与优化方案 --- #### **一、核心原因分析** 根据引用内容,频繁 Full GC 的根本原因可分为四类: 1. **内存配置不当** - 堆内存分配不合理(新生代过小导致对象提前晋升老年代)[^3] - 元空间溢出:`Metaspace` 不足触发 Full GC[^2] - 大对象直接进入老年代(如未分片的超大数组) 2. **GC 策略缺陷** - 使用 CMS/Parallel GC 处理大堆时 STW 时间长[^2] - 未启用自适应调节策略(如 `-XX:+UseAdaptiveSizePolicy`) 3. **内存泄漏** - 静态集合类持有对象引用(如 `static Map`) - 未关闭的资源(数据库连接、文件流) - 线程局部变量未清理(`ThreadLocal` 滥用) 4. **代码与并发问题** - 高频创建临时对象(如日志拼接、JSON 序列化)[^3] - 线程池配置过大:线程栈总内存超过新生代容量 - 请求速率超过系统处理能力(对象创建速度 > GC 回收速度)[^3] --- #### **二、优化解决方案** ##### **1. 内存与 GC 参数调优** ```bash # 基础配置(示例:8G 堆内存应用) -Xms8g -Xmx8g # 堆内存固定避免震荡 -Xmn4g # 新生代占堆50%(推荐比例) -XX:MetaspaceSize=512m # 元空间初始大小[^2] -XX:MaxMetaspaceSize=1g # 防止元空间膨胀 -XX:+UseG1GC # G1 替代 CMS/Parallel[^2] -XX:MaxGCPauseMillis=200 # 目标停顿时间 -XX:InitiatingHeapOccupancyPercent=45 # 更早启动混合GC ``` **关键参数说明**: - **新生代比例**:新生代过小会导致 Minor GC 频繁,对象过早晋升老年代。设 $S_{\text{new}}$ 为新生代大小,$S_{\text{heap}}$ 为堆大小,建议: $$ S_{\text{new}} \geq 0.4 \times S_{\text{heap}} $$ - **G1 优势**:通过 Region 分片和增量回收,减少 Full GC 触发概率[^2] ##### **2. 内存泄漏排查** - **步骤 1**:生成内存快照 ```bash jmap -dump:live,format=b,file=heap.hprof <pid> ``` - **步骤 2**:分析支配树(MAT/Eclipse Memory Analyzer) - 查找 `java.lang.ThreadLocal` 或静态集合类 - 检查 `SoftReference` 对象存活时间(调整 `-XX:SoftRefLRUPolicyMSPerMB`[^4]) - **步骤 3**:监控对象分配 ```bash jstat -gcutil <pid> 1000 # 每1秒输出GC统计 ``` ##### **3. 代码与架构优化** - **避免临时对象**: - 用 `StringBuilder` 替代字符串拼接 - 对象池化(如 Apache Commons Pool) - **线程池改造**: ```java // 错误示范:无界队列导致内存堆积 Executors.newCachedThreadPool(); // 正确方案:限制队列长度 new ThreadPoolExecutor(core, max, 60s, new ArrayBlockingQueue<>(1000)); ``` - **请求限流**: - 当 QPS > 系统阈值时,启用熔断(如 Sentinel/Hystrix) ##### **4. 监控与应急措施** - **实时预警**: - 监控 `jvm_gc_pause_seconds_max{action="end of major GC"}` - 规则:`Full GC 频率 > 1次/分钟` 触发告警 - **应急方案**: - 临时扩容堆内存:`kill -3 <pid> && jmap -heap <pid>` - 重启时添加参数:`-XX:+ExplicitGCInvokesConcurrent`(禁止 System.gc() 触发 Full GC) --- ### 优化效果验证 1. **监控指标**: - Full GC 频率下降至 < 1次/天 - 老年代内存占用曲线平稳(无持续上升) 2. **压测验证**: - 模拟峰值流量(如 JMeter),观察 Full GC 触发条件 > **核心原则**: > - 优先解决**内存泄漏**(治本) > - 参数调优是**临时手段**(治标)[^1] > - 高并发场景需结合**限流+代码优化**[^3] --- ### 相关问题 1. 如何通过 GC 日志分析 Full GC 的具体原因? 2. G1 垃圾回收器的调优参数有哪些最佳实践? 3. 哪些工具可以实时监控 JVM 内存泄漏? 4. 如何区分 Full GC 是由内存泄漏还是流量突增引起的? 5. 在容器化环境(如 Kubernetes)中如何配置 JVM 内存参数?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值