Skip to content

GC 核心概念(System.gc/STW/安全点/引用类型)

GC 核心概念全景

这一节把 GC 中最重要的几个核心概念放在一起,帮助你建立完整的知识体系。

System.gc()

作用与行为

System.gc() 建议 JVM 执行 GC,但不保证立即执行

java
public class SystemGCDemo {
    public static void main(String[] args) {
        // 创建大量对象
        byte[] data = new byte[1024 * 1024 * 10];  // 10MB

        // 建议 GC
        System.gc();  // 不保证立即执行,只是「建议」
    }
}

是否一定执行 FullGC

System.gc() 并不总是执行 FullGC。JVM 会根据情况决定:

  • 如果使用 G1:执行 Mixed GC(年轻代 + 部分老年代)
  • 如果使用 Serial/Parallel:执行 FullGC
  • 如果使用 CMS:先 YoungGC,条件满足时执行 FullGC

禁用 System.gc()

bash
# 禁用 System.gc() 调用
java -XX:+DisableExplicitGC MyApp

# System.gc() 调用会被 JVM 忽略
# 适合不想被显式 GC 干扰的场景

RMI 的定时 GC

bash
# RMI 每小时自动调用一次 System.gc()
# 用来清理远程对象
# 如果禁用显式 GC,会影响 RMI 的内存管理
-Dsun.rmi.dgc.client.gcInterval=3600000
-Dsun.rmi.dgc.server.gcInterval=3600000

Stop-The-World(STW)

什么是 STW

Stop-The-World(STW) 是指 GC 期间,暂停所有应用线程(Java 线程和 native 线程),直到 GC 完成。

应用线程运行:
  ──────▶ 时间 ────────────────────────────▶

GC 开始(STW):
  ══════▶ 时间 ───▶

所有 Java 线程暂停
GC 线程执行
所有 Java 线程恢复
  ──────▶ 时间 ────────────────────────────▶

哪些阶段需要 STW

GC 阶段是否 STW说明
初始标记(Initial Mark)是(短暂)标记 GC Roots 直接引用的对象
并发标记(Concurrent Mark)GC 线程和应用线程并发执行
再标记(Remark)修正并发标记期间产生的变化
并发清除(Concurrent Sweep)GC 线程和应用线程并发执行
并发重置(Concurrent Reset)重置 GC 状态
复制/清除/压缩是(FullGC 时)移动对象必须 STW

STW 的影响

影响说明
响应延迟停顿期间应用无法响应用户请求
吞吐量下降GC 线程消耗 CPU
服务不可用长停顿导致服务超时

安全点(Safe Point)

为什么需要安全点

GC 需要在「安全」的时刻暂停线程——即所有线程都不在修改对象引用的状态。如果线程正在修改引用时暂停,GC 可能漏掉引用变更。

安全点(Safe Point) 就是这样的「安全」位置:线程在此处停顿不会影响 GC 的正确性。

安全点的位置

JVM 在以下位置设置安全点:

java
public class SafePointLocations {
    public static void main(String[] args) {
        // 方法调用返回时
        String s = args[0].toString();  // ◀ 方法返回处

        // 循环跳转时
        for (int i = 0; i < 100; i++) {  // ◀ 循环回边
            s = s + i;
        }

        // 异常抛出时
        try {
            int x = Integer.parseInt(s);
        } catch (NumberFormatException e) {  // ◀ 异常抛出处
        }
    }
}

安全点的触发条件

线程到达安全点后,才能被 JVM 挂起。触发条件:

触发方式说明
主动挂起线程到达安全点后主动检查是否需要挂起
VMThread 轮询VMThread 轮询各线程的安全点状态
抢先式中断VMThread 直接中断所有线程,在安全点恢复
主动轮询线程在安全点轮询 Safe Point Request 标志

安全点与长循环

有一个常见的性能问题:长循环导致线程无法到达安全点

java
public class LongLoop implements Runnable {
    public void run() {
        while (true) {  // ◀ 这个循环可能导致线程无法到达安全点
            // 长时间占用 CPU,不给线程检查安全点的机会
            // GC 无法安全挂起这个线程
            process();
        }
    }
}

解决方案:JVM 会在循环回边(loop back edge)插入安全点检查。

Java 的四种引用类型

Java 提供了四种引用强度不同的引用类型,从强到弱:

1. 强引用(Strong Reference)

java
// 最常见的引用
Object obj = new Object();  // 强引用
obj = null;  // 取消引用
// GC 不会回收有强引用指向的对象

2. 软引用(Soft Reference)

java
import java.lang.ref.SoftReference;

public class SoftReferenceDemo {
    public static void main(String[] args) {
        // 软引用:内存不足时才回收
        SoftReference<byte[]> soft = new SoftReference<>(new byte[1024 * 1024 * 10]);

        // 软引用引用的对象可能在 GC 时被回收
        // 回收后,soft.get() 返回 null
        byte[] data = soft.get();
    }
}

用途:缓存——内存充足时保留,内存不足时释放。

3. 弱引用(Weak Reference)

java
import java.lang.ref.WeakReference;

public class WeakReferenceDemo {
    public static void main(String[] args) {
        // 弱引用:下一次 GC 时必定回收
        WeakReference<byte[]> weak = new WeakReference<>(new byte[1024 * 1024]);

        // 无论内存是否充足,下一次 GC 都会回收这个对象
        // weak.get() 在 GC 后返回 null
    }
}

用途WeakHashMap——key 不再使用时自动删除 entry。

4. 虚引用(Phantom Reference)

java
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class PhantomReferenceDemo {
    public static void main(String[] args) {
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
        PhantomReference<byte[]> phantom = new PhantomReference<>(
            new byte[1024], queue);

        // 虚引用:无法通过 get() 获取对象,永远返回 null
        // 对象被回收后,phantom 进入 ReferenceQueue
        // 用于追踪对象被回收的事件

        // 用 ReferenceQueue 监听虚引用
        // 线程可以阻塞等待,直到虚引用进入队列
    }
}

用途:追踪对象被 GC 回收、清理本地资源。

四种引用的对比

引用类型回收时机get() 返回值典型用途
强引用永不自动回收(除非 GC Roots 断开)对象本身普通变量
软引用内存不足时回收对象本身或 null缓存
弱引用下一次 GC 时回收对象本身或 null规范化映射
虚引用无法通过引用访问永远 null追踪回收事件

ReferenceQueue:引用的通知机制

java
public class ReferenceQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        ReferenceQueue<Object> queue = new ReferenceQueue<>();
        WeakReference<Object> ref = new WeakReference<>(new Object(), queue);

        // ref 引用的对象被 GC 回收后
        // ref 会被加入 queue
        System.gc();
        Thread.sleep(100);

        // 从 queue 中取出被回收的引用
        Reference<? extends Object> polled = queue.poll();
        if (polled != null) {
            System.out.println("对象已被 GC 回收");
        }
    }
}

本节小结

GC 核心概念一览:

概念说明
System.gc()建议 GC,不保证立即执行
Stop-The-WorldGC 期间暂停所有应用线程
安全点线程可以安全停顿的位置,用于 GC
强引用普通引用,永不自动回收
软引用内存不足时回收,用于缓存
弱引用下次 GC 时回收,用于规范化映射
虚引用无法获取对象,用于追踪回收事件

理解这些核心概念,是理解 GC 调优和问题排查的基础。

下一节,我们来看 内存溢出 vs 内存泄漏(分析/案例)

基于 VitePress 构建