Full GC 的深度解析与排查流程详解

Full GC 的深度解析与排查流程详解

一、Full GC 的定义与核心概念

Full GC(Full Garbage Collection,全量垃圾回收)​ 是 JVM 垃圾回收机制中最彻底、最耗时的回收过程,其核心特点如下:

1. ​Full GC 的触发条件

触发条件

说明

​老年代空间不足

当对象从年轻代晋升到老年代时,若老年代剩余空间不足(无法容纳新晋升对象),触发 Full GC。

​元空间(Metaspace)不足

类元数据(如加载的类信息)占满元空间。

​显式调用 System.gc()

代码中主动调用垃圾回收(实际是否执行由 JVM 决定)。

​堆外内存(Direct Memory)不足

使用 NIO 分配的堆外内存超出限制(需通过 -XX:MaxDirectMemorySize 控制)。

2. ​Full GC 的工作范围

回收区域 :

Full GC 会清理 整个堆内存 (包括年轻代、老年代)以及 元空间(部分 GC 算法可能不回收元空间)。

回收对象 :

清理所有不再被引用的对象(即"垃圾"),并整理内存碎片。

3. ​Full GC 的性能影响

Stop-The-World(STW)​ :

Full GC 会暂停所有应用线程(STW),导致服务完全不可用。

例如:若 Full GC 耗时 5秒,所有用户请求将被阻塞 5 秒,引发超时或熔断。

耗时对比 :

GC 类型

平均耗时

频率

​Young GC

10ms ~ 100ms

高(分钟级)

​Full GC

1s ~ 10s

低(小时级)

二、Full GC 与内存问题的关联

Full GC ​既是内存问题的结果,也是内存问题的表现:

内存泄漏 :

对象因代码缺陷无法回收,导致老年代逐渐填满,频繁触发 Full GC。

典型表现 :Full GC 后老年代内存占用率仍高于 70%(通过 jstat -gcutil 监控)。

内存分配过小 :

堆内存设置过小(如 -Xmx=1g),年轻代晋升速度超过老年代容量,频繁触发 Full GC。

元空间泄漏 :

动态类加载(如反射生成类)未清理,导致元空间溢出,触发 Full GC。

三、结合图片步骤分析 Full GC 问题

根据图片中的三步流程,以下是针对 Full GC 的具体操作:

1. ​将内存堆栈导出(Heap Dump)​

目标 :获取 Full GC 触发时的内存快照,分析对象分布。

操作命令:

复制代码

# 在 Full GC 发生时自动导出 Heap Dump

jmap -dump:format=b,file=full_gc_dump.hprof

关键参数解析:

**format=b**:二进制格式,兼容分析工具。

**file=full_gc_dump.hprof**:堆转储文件名。

****:Java 进程 ID(通过 jps 或 ps -ef \| grep java 获取)。

2. ​用 MAT 内存工具分析

目标 :定位占用内存最多的对象,分析其引用链是否合理。

MAT 操作步骤:

打开 Heap Dump 文件 :

File → Open Heap Dump → 选择 full_gc_dump.hprof。

分析支配树(Dominator Tree)​ :

路径:OverView → Dominator Tree。

作用:显示哪些对象直接或间接占用了最多内存。

Full GC 关联:若发现大量本应被回收的对象(如缓存条目),说明存在内存泄漏。

查看 GC Roots 引用链 :

右键可疑对象 → Path to GC Roots → exclude weak/soft references。

作用:确认对象是否被强引用(如静态变量)持有,导致无法回收。

3. ​结合代码找到问题点

目标 :修复代码中的内存泄漏或优化内存分配策略。

代码示例与解析:

java

复制代码

public class CacheManager {

private static Map cache = new HashMap<>(); // 静态 Map 导致对象无法释放

public void addToCache(String key, Object value) {

cache.put(key, value);

}

// 缺失清理逻辑:没有 remove 方法或过期策略

}

问题分析:

静态 Map 是 GC Root,所有存入的 Object 会一直存活,导致老年代被填满,频繁触发 Full GC。

修复方案:

java

复制代码

public class FixedCacheManager {

/**

* 缓存存储容器。

* 使用 WeakHashMap 实现:

* - Key 使用弱引用(WeakReference),当外部没有强引用指向 Key 时,条目自动被垃圾回收。

* - Value 被 Entry 对象间接持有(需注意 Value 本身不能有对 Key 的强引用,否则会导致 Key 无法回收)。

*

* 参数说明:

* new WeakHashMap<>(16, 0.75f)

* - 16:初始容量(默认16,按需调整)

* - 0.75f:负载因子(达到75%容量时自动扩容)

*/

private static Map cache = new WeakHashMap<>(16, 0.75f);

/**

* 添加对象到缓存

* @param key 缓存键(需确保外部不会长期持有此对象的强引用)

* @param value 缓存值(注意:如果 Value 持有 Key 的强引用,会导致 Key 无法回收)

*/

public void addToCache(String key, Object value) {

cache.put(key, value); // 若 Key 已被回收,此操作相当于无效

}

/**

* 手动清理缓存(LRU 策略的简化实现)

* @param maxSize 允许的最大缓存条目数,超过此值时触发清理

*

* 实现逻辑说明:

* 1. 检查当前缓存大小是否超过阈值

* 2. 使用迭代器遍历键集合

* 3. 删除最旧的条目(此处仅为示例,实际 LRU 需要记录访问顺序)

*

* 注意:WeakHashMap 本身不维护插入顺序,此处只是简单删除第一个元素,

* 更严谨的 LRU 实现应使用 LinkedHashMap 或第三方缓存库(如 Caffeine)

*/

public void cleanup(int maxSize) {

// 检查当前缓存大小

if (cache.size() > maxSize) {

// 获取键集合的迭代器

Iterator it = cache.keySet().iterator();

// 确保至少有一个元素(防止 NoSuchElementException)

if (it.hasNext()) {

// 获取第一个键(不保证是最久未访问的)

it.next();

// 通过迭代器删除当前元素(安全删除方式)

it.remove();

}

}

}

}

改进点:

WeakHashMap:键(Key)是弱引用,当键不再被外部强引用时,条目自动删除。

LRU 策略:限制缓存大小,防止内存无限增长。

四、Full GC 的优化实践

1. ​JVM 参数调优

bash

复制代码

-XX:+UseG1GC # 启用 G1 垃圾回收器(低延迟、高吞吐)

-XX:MaxGCPauseMillis=200 # 目标最大 GC 暂停时间(单位:毫秒)

-XX:InitiatingHeapOccupancyPercent=45 # 老年代占用 45% 时触发并发标记(避免 Full GC)

2. ​监控与告警

监控工具 :

使用 jstat -gcutil 1000 实时监控 GC 状态:

复制代码

S0 S1 E O M CCS YGC YGCT FGC FGCT GCT

0.00 0.00 6.25 70.30 95.12 90.45 100 2.500 5 15.000 17.500

O(Old)列:老年代内存占用率(70.30%),若持续高于阈值需优化。

FGC/FGCT:Full GC 次数(5 次)与总耗时(15 秒)。

五、总结

Full GC 的本质:JVM 内存管理的最后防线,但频繁 Full GC 是系统危险的信号。

排查流程:导出堆转储 → MAT 分析 → 代码修复。

终极目标:减少 Full GC 频率(通过参数调优)和耗时(通过代码优化)。

参考:腾讯元宝