一、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 <PID>
关键参数解析:
- **
format=b
**:二进制格式,兼容分析工具。 - **
file=full_gc_dump.hprof
**:堆转储文件名。 - **
<PID>
**: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<String, Object> 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<String, Object> 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<String> 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 <PID> 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 频率(通过参数调优)和耗时(通过代码优化)。