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=3600000Stop-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-World | GC 期间暂停所有应用线程 |
| 安全点 | 线程可以安全停顿的位置,用于 GC |
| 强引用 | 普通引用,永不自动回收 |
| 软引用 | 内存不足时回收,用于缓存 |
| 弱引用 | 下次 GC 时回收,用于规范化映射 |
| 虚引用 | 无法获取对象,用于追踪回收事件 |
理解这些核心概念,是理解 GC 调优和问题排查的基础。
下一节,我们来看 内存溢出 vs 内存泄漏(分析/案例)。
