JVM 垃圾回收机制详解
详解 JVM 内存模型、垃圾回收算法与 GC 调优实战
目录
什么是垃圾回收
垃圾回收(Garbage Collection,GC)是 Java 虚拟机自动管理内存的机制,负责回收不再使用的对象,释放内存空间。
JVM 内存结构
JVM 内存区域
├── 堆(Heap)
│ ├── 年轻代(Young Generation)
│ │ ├── Eden 区
│ │ ├── Survivor From
│ │ └── Survivor To
│ └── 老年代(Old Generation)
├── 方法区(Method Area)/ 元空间(Metaspace)
├── 虚拟机栈(VM Stack)
├── 本地方法栈(Native Method Stack)
└── 程序计数器(Program Counter)判断对象是否可回收
1. 引用计数法
每个对象维护一个引用计数器,引用为 0 时回收。
缺点:无法解决循环引用问题。
2. 可达性分析算法(Java 采用)
从 GC Roots 出发,不可达的对象可被回收。
GC Roots 包括:
- 虚拟机栈中的局部变量
- 方法区中类静态属性
- 方法区中常量
- 本地方法栈中 JNI 引用
- 所有被同步锁持有的对象
public void method() {
Object obj = new Object(); // obj 是 GC Root
// obj 还在栈帧中,不会被回收
} // 方法结束,obj 出栈,对象可被回收垃圾回收算法
1. 标记-清除算法(Mark-Sweep)
过程:
- 标记所有存活对象
- 清除未标记的对象
缺点:产生内存碎片
2. 复制算法(Copying)
过程:
- 将内存分为两块
- 只使用其中一块
- 回收时将存活对象复制到另一块
- 清空当前块
适用:年轻代(Eden + Survivor)
3. 标记-整理算法(Mark-Compact)
过程:
- 标记存活对象
- 将存活对象向一端移动
- 清理边界外的内存
适用:老年代
垃圾收集器
1. Serial 收集器
单线程收集器,STW(Stop The World)
-XX:+UseSerialGC特点:简单高效,适合单核/小内存环境
2. ParNew 收集器
Serial 的多线程版本
-XX:+UseParNewGC特点:配合 CMS 使用
3. Parallel Scavenge 收集器
吞吐量优先收集器
-XX:+UseParallelGC
-XX:MaxGCPauseMillis=200 最大停顿时间
-XX:GCTimeRatio=99 GC时间占比特点:关注吞吐量,适合后台计算
4. CMS 收集器(Concurrent Mark Sweep)
低延迟收集器
-XX:+UseConcMarkSweepGC过程:
- 初始标记(STW,很短)
- 并发标记
- 重新标记(STW)
- 并发清除
缺点:
- 产生内存碎片
- CPU 敏感
- 浮动垃圾
5. G1 收集器(Garbage First)
分区收集器
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200特点:
- 将整个堆划分为多个 Region
- 可预测的停顿时间
- 适合大堆内存(6G+)
收集过程:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
6. ZGC 和 Shenandoah
低延迟收集器(JDK 11+)
-XX:+UseZGC
-XX:+UseShenandoahGC目标:毫秒级停顿(< 10ms)
GC 调优参数
堆内存设置
# 初始堆大小
-Xms2g
# 最大堆大小
-Xmx2g
# 年轻代大小
-Xmn512m
# 元空间大小
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=256mGC 日志
# JDK 8
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/path/to/gc.log
# JDK 11+
-Xlog:gc*:file=/path/to/gc.log:time:filecount=5,filesize=100mG1 调优
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=16m
-XX:InitiatingHeapOccupancyPercent=45GC 问题排查
1. 查看 GC 情况
# 实时查看
jstat -gcutil <pid> 1000
# 输出示例
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 12.50 35.20 45.30 95.12 92.10 123 2.345 5 1.234 3.579字段说明:
- S0/S1:Survivor 区使用率
- E:Eden 区使用率
- O:老年代使用率
- M:元空间使用率
- YGC/YGCT:年轻代 GC 次数/时间
- FGC/FGCT:Full GC 次数/时间
2. 生成堆转储
# 手动生成
jmap -dump:format=b,file=heap.hprof <pid>
# OOM 时自动生成
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/path/to/dumps3. 分析工具
- VisualVM:可视化监控
- Eclipse MAT:堆转储分析
- GCViewer:GC 日志分析
- Arthas:实时诊断
常见 GC 问题
1. 内存泄漏
// 典型场景:静态集合持有对象
public class MemoryLeak {
private static final List<Object> list = new ArrayList<>();
public void add(Object obj) {
list.add(obj); // 永远不释放
}
}2. 频繁 Full GC
原因:
- 老年代空间不足
- 大对象直接进入老年代
- 内存泄漏
解决:
- 增大堆内存
- 调整晋升阈值
- 检查内存泄漏
3. GC 停顿过长
解决:
- 使用 G1/ZGC 收集器
- 减小堆大小
- 优化对象分配
GC 调优示例
场景 1:高并发 Web 服务
# 4核8G 服务器
-Xms4g -Xmx4g
-Xmn1536m
-XX:+UseG1GC
-XX:MaxGCPauseMillis=100
-XX:+ParallelRefProcEnabled场景 2:大数据处理
# 8核32G 服务器
-Xms16g -Xmx16g
-XX:+UseG1GC
-XX:G1HeapRegionSize=32m
-XX:MaxGCPauseMillis=500场景 3:微服务
# 2核4G 容器
-Xms2g -Xmx2g
-XX:+UseG1GC
-XX:MaxDirectMemorySize=512m
-XX:+UseContainerSupport代码层面的优化
1. 避免创建大对象
// 不好:直接创建大数组
byte[] data = new byte[1024 * 1024 * 10]; // 10MB
// 好:分批处理
for (int i = 0; i < chunks; i++) {
byte[] chunk = new byte[1024 * 1024]; // 1MB
process(chunk);
}2. 使用对象池
public class ObjectPool<T> {
private final Queue<T> pool;
public T borrow() {
T obj = pool.poll();
return obj != null ? obj : create();
}
public void returns(T obj) {
pool.offer(obj);
}
}3. 及时释放引用
public void process() {
BigObject obj = new BigObject();
// 使用 obj
obj = null; // 及时释放,帮助 GC
// 长时间操作...
}总结
理解 JVM 垃圾回收机制对于编写高性能 Java 应用至关重要。关键要点:
- 选择合适的收集器:小堆用 Parallel,大堆用 G1,低延迟用 ZGC
- 合理设置堆大小:避免过大或过小
- 监控和调优:通过 GC 日志和监控工具持续优化
- 代码优化:减少对象创建,及时释放引用