目录

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)

过程

  1. 标记所有存活对象
  2. 清除未标记的对象

缺点:产生内存碎片

2. 复制算法(Copying)

过程

  1. 将内存分为两块
  2. 只使用其中一块
  3. 回收时将存活对象复制到另一块
  4. 清空当前块

适用:年轻代(Eden + Survivor)

3. 标记-整理算法(Mark-Compact)

过程

  1. 标记存活对象
  2. 将存活对象向一端移动
  3. 清理边界外的内存

适用:老年代

垃圾收集器

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

过程

  1. 初始标记(STW,很短)
  2. 并发标记
  3. 重新标记(STW)
  4. 并发清除

缺点

  • 产生内存碎片
  • CPU 敏感
  • 浮动垃圾

5. G1 收集器(Garbage First)

分区收集器
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200

特点

  • 将整个堆划分为多个 Region
  • 可预测的停顿时间
  • 适合大堆内存(6G+)

收集过程

  1. 初始标记
  2. 并发标记
  3. 最终标记
  4. 筛选回收

6. ZGC 和 Shenandoah

低延迟收集器(JDK 11+)
-XX:+UseZGC
-XX:+UseShenandoahGC

目标:毫秒级停顿(< 10ms)

GC 调优参数

堆内存设置

# 初始堆大小
-Xms2g

# 最大堆大小
-Xmx2g

# 年轻代大小
-Xmn512m

# 元空间大小
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=256m

GC 日志

# JDK 8
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/path/to/gc.log

# JDK 11+
-Xlog:gc*:file=/path/to/gc.log:time:filecount=5,filesize=100m

G1 调优

-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:G1HeapRegionSize=16m
-XX:InitiatingHeapOccupancyPercent=45

GC 问题排查

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/dumps

3. 分析工具

  • 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 应用至关重要。关键要点:

  1. 选择合适的收集器:小堆用 Parallel,大堆用 G1,低延迟用 ZGC
  2. 合理设置堆大小:避免过大或过小
  3. 监控和调优:通过 GC 日志和监控工具持续优化
  4. 代码优化:减少对象创建,及时释放引用