Skip to content

JVM 内存模型与 GC

GC 是 Java 的核心特性,也是性能调优的关键。理解各种 GC 算法和调优参数,是高级 Java 工程师的必备技能。

GC 基础概念

哪些对象需要回收?

java
// 可达性分析算法(Reachability Analysis)
// GC Roots 包括:
// 1. 虚拟机栈中引用的对象(局部变量)
// 2. 方法区中静态属性引用的对象
// 3. 方法区中常量引用的对象
// 4. 本地方法栈中 JNI 引用的对象
// 5. JVM 内部引用(基本类型 Class 对象、常驻异常对象等)
// 6. 同步锁持有的对象

// 从 GC Roots 出发,不可达的对象就是"垃圾"
Object a = new Object();  // a 可达
Object b = new Object();  // b 可达
a = null;                 // 原来 a 指向的对象不可达,可被回收

引用类型

java
import java.lang.ref.*;

// 强引用(Strong Reference)— 不会被 GC 回收
Object strong = new Object();

// 软引用(Soft Reference)— 内存不足时回收,适合缓存
SoftReference<byte[]> soft = new SoftReference<>(new byte[1024 * 1024]);
byte[] data = soft.get();  // 可能为 null

// 弱引用(Weak Reference)— 下次 GC 时回收
WeakReference<Object> weak = new WeakReference<>(new Object());
// WeakHashMap 使用弱引用作为 key

// 虚引用(Phantom Reference)— 无法通过虚引用获取对象,用于跟踪 GC 回收
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantom = new PhantomReference<>(new Object(), queue);

GC 算法

标记-清除(Mark-Sweep)

优点:简单
缺点:产生内存碎片,清除效率低

标记阶段:从 GC Roots 遍历,标记所有可达对象
清除阶段:回收未标记对象

[已用][已用][空闲][已用][空闲][已用][空闲][空闲]
                  ↓ 清除后
[已用][已用][    ][已用][    ][已用][         ]
                  碎片化!

标记-复制(Mark-Copy)

优点:无碎片,分配效率高
缺点:内存利用率 50%

将内存分为两半,每次只用一半
GC 时将存活对象复制到另一半,清空当前半

年轻代 Eden + Survivor 就是此算法的改进版:
Eden(80%) + S0(10%) + S1(10%)
每次 Minor GC:Eden + S0 → S1(或反向)

标记-整理(Mark-Compact)

优点:无碎片,内存利用率高
缺点:整理阶段需要移动对象,开销大

标记后将存活对象向一端移动,清理边界外的内存
老年代常用此算法

垃圾收集器

Serial GC(串行)

bash
-XX:+UseSerialGC
# 单线程,STW(Stop The World)
# 适合:客户端应用、小内存场景

Parallel GC(并行,Java 8 默认)

bash
-XX:+UseParallelGC
-XX:ParallelGCThreads=4    # GC 线程数
-XX:MaxGCPauseMillis=200   # 最大停顿时间目标
-XX:GCTimeRatio=99         # GC 时间占比目标(1/(1+99)=1%)
# 多线程 GC,吞吐量优先
# 适合:批处理、后台计算

CMS GC(并发标记清除,已废弃)

bash
-XX:+UseConcMarkSweepGC
# 并发执行,低停顿
# 缺点:内存碎片、CPU 占用高、并发模式失败
# Java 9 废弃,Java 14 移除

G1 GC(Java 9+ 默认)

G1 将堆划分为多个等大的 Region(默认 1-32MB)
每个 Region 可以是 Eden/Survivor/Old/Humongous

优势:
- 可预测的停顿时间(-XX:MaxGCPauseMillis)
- 无内存碎片(整理)
- 适合大堆(6GB+)
bash
-XX:+UseG1GC                    # Java 9+ 默认
-XX:MaxGCPauseMillis=200        # 停顿时间目标(ms)
-XX:G1HeapRegionSize=16m        # Region 大小
-XX:G1NewSizePercent=5          # 年轻代最小占比
-XX:G1MaxNewSizePercent=60      # 年轻代最大占比
-XX:G1MixedGCLiveThresholdPercent=85  # 混合 GC 触发阈值

ZGC(Java 15+ 生产可用)

bash
-XX:+UseZGC
# 停顿时间 < 1ms(与堆大小无关)
# 并发标记、并发整理
# 适合:超大堆(TB 级)、低延迟场景
# Java 21 支持分代 ZGC(-XX:+ZGenerational)

Shenandoah GC

bash
-XX:+UseShenandoahGC
# 类似 ZGC,Red Hat 开发
# 并发整理,极低停顿

GC 日志分析

bash
# 开启 GC 日志(Java 9+)
-Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=5,filesize=20m

# Java 8
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log
# G1 GC 日志示例
[2.345s][info][gc] GC(1) Pause Young (Normal) (G1 Evacuation Pause)
[2.345s][info][gc,heap] GC(1) Eden regions: 24->0(24)
[2.345s][info][gc,heap] GC(1) Survivor regions: 3->3(3)
[2.345s][info][gc,heap] GC(1) Old regions: 10->10
[2.345s][info][gc,heap] GC(1) Humongous regions: 0->0
[2.345s][info][gc,heap] GC(1) Metaspace: 45678K->45678K(1093632K)
[2.345s][info][gc] GC(1) Pause Young (Normal) 312M->52M(512M) 12.345ms
#                                              回收前->回收后(堆大小) 停顿时间

常见 GC 问题

Full GC 频繁

java
// 原因 1:老年代空间不足
// 解决:增大堆内存,优化对象生命周期

// 原因 2:元空间不足
// 解决:-XX:MaxMetaspaceSize=512m

// 原因 3:System.gc() 被调用
// 解决:-XX:+DisableExplicitGC

// 原因 4:大对象直接进入老年代
// 解决:-XX:PretenureSizeThreshold=3145728(3MB)

内存泄漏排查

bash
# 1. 生成堆转储
jmap -dump:format=b,file=heap.hprof <pid>
# 或 OOM 时自动生成
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/

# 2. 用 MAT(Memory Analyzer Tool)分析
# 查找 Leak Suspects
# 分析 Dominator Tree
# 找到持有大量内存的对象链
java
// 常见内存泄漏场景
// 1. 静态集合持有对象引用
static List<Object> cache = new ArrayList<>();
// 解决:使用 WeakReference 或定期清理

// 2. 未关闭的资源
// 解决:try-with-resources
try (Connection conn = dataSource.getConnection()) {
    // 自动关闭
}

// 3. 内部类持有外部类引用
// 解决:使用静态内部类

// 4. ThreadLocal 未清理
ThreadLocal<Object> tl = new ThreadLocal<>();
tl.set(largeObject);
// 解决:使用后调用 tl.remove()

GC 调优实战

bash
# 典型生产配置(Java 17,8核16G)
java \
  -Xms4g -Xmx4g \
  -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=200 \
  -XX:G1HeapRegionSize=16m \
  -XX:MetaspaceSize=256m \
  -XX:MaxMetaspaceSize=512m \
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/app/logs/ \
  -Xlog:gc*:file=/app/logs/gc.log:time,uptime:filecount=5,filesize=20m \
  -jar app.jar

# 低延迟场景(Java 21,ZGC)
java \
  -Xms8g -Xmx8g \
  -XX:+UseZGC \
  -XX:+ZGenerational \
  -XX:MaxGCPauseMillis=10 \
  -jar app.jar

GC 选择指南

场景推荐 GC
吞吐量优先(批处理)Parallel GC
通用 Web 服务G1 GC(默认)
低延迟(< 10ms)ZGC / Shenandoah
超大堆(> 32GB)ZGC
Java 21 新项目ZGC(分代)

系统学习 Java 生态,深入底层架构