在程序的世界里,内存如同城市的地基,承载着每一行代码的运转。而垃圾收集器(Garbage Collector, GC)则是这座城市的清洁系统,它日夜巡视,回收那些不再被需要的"建筑残骸"------即程序运行中产生的垃圾对象。但究竟什么样的数据会被判定为"垃圾"?是我们主动丢弃的变量,还是某种隐秘的规则在幕后裁决?
一、显示产生
开发者主动操作导致对象不可达
- 置空引用
ini
Object obj = new Object();
obj = null;
局部变量obj是个引用,存放在当前方法的栈帧的局部变量表中。
new Object()产生的对象实例放在堆中。
当把obj置空后,没有其他引用指向堆中的对象实例,这个对象实例就变成了垃圾。
obj置空了,那obj本身是不是也变成了垃圾?需要GC清理吗?
确实是垃圾,但是不需要GC的参与。栈帧从虚拟机栈弹出,内存立即释放。
- 局部变量超出作用域(方法级)
javascript
void doSomething() {
Object obj = new Object();
}
方法结束后,栈帧中的局部变量(如 obj
)被自动释放,堆中的对象失去唯一强引用,变成垃圾。
- 局部变量超出作用域(代码块级)
ini
void doSomething() {
for (int i = 0; i < 10; i++) {
Object obj = new Object();
}
}
这里的i和obj在编译期确定在局部变量表中的槽位(Slot)索引。
每次进入循环,堆上都产生一个对象实例,而对应槽位上的值(引用)会覆盖修改,指向新的对象实例。
下一次循环会导致上一次循环产生的实例对象的应用被断开(因为指向了新的),形成垃圾。
最后一次产生的对象因为方法的结束,栈帧被弹出,引用消失(释放),失去了引用,变成垃圾。
二、隐式产生
对象因逻辑操作(如集合操作、静态变量变更)失去引用,但开发者未显式干预。
- 集合清空或者移除元素
ini
List<Object> list = new ArrayList<>();
list.add(new Object());
list.remove(0);
list是栈帧中的一个引用,指向堆内存中new ArrayList<>()产生的实例对象。
这个实例对象中,有一个引用elementData指向了堆内存的一块区域。
当 list.add(new Object());时,new Object()在堆内存产生了一个对象,上面的那块区域中0号索引修改成了new Object()产生对象的地址。
当list.remove(0);时,0号索引修改成了null(无指向),此时堆内存中的对象变成了垃圾。
- 静态变量重新赋值
typescript
static Object ref = new Object();
public static void main(String[] args) {
GCTest.ref = new Object();
}
在类加载时,方法区(JDK 8 及之后,静态变量存储在堆内存中的类对象中)中有一个引用ref指向了new Object()在对堆上产生的对象实例。
main方法中将方法区(或堆中的类对象)中ref修改成了new Object();产生的新对象实例。老对象实例失去引用,变成垃圾。
三、动态产生
- 类卸载
ini
ClassLoader loader = new CustomClassLoader();
Class<?> clazz = loader.loadClass("MyClass");
loader = null;
loader变量强引用CustomClassLoader实例。
loader = null断开了强引用,但需检查是否有其他引用:
- 隐式引用 :JVM在加载类时,
MyClass
的Class
对象会隐式引用其ClassLoader
。 - 显式引用 :若
clazz
变量仍持有MyClass
的Class
对象,则ClassLoader
仍被隐式引用。
四、特定场景产生
- 软引用(SoftReference)
javascript
SoftReference<Object> softRef = new SoftReference<>(new Object());
- 创建了一个
Object
实例(强引用指向它)。 - 将该对象包装到
SoftReference
中,此时对象仅通过软引用可达。 - 代码执行后,原强引用(
new Object()
的临时引用)消失,对象仅由软引用softRef
关联。 - 内存不足时,尝试回收仅被软引用关联的对象(被当成垃圾)。
- 弱引用(WeakReference)
dart
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj);
// 情况1:存在强引用(obj未置空)→ 对象不会被回收
System.gc();
assert weakRef.get() != null; // 对象存活
// 情况2:解除强引用 → 对象被回收
obj = null;
System.gc();
assert weakRef.get() == null; // 对象被回收
- 弱引用对象的回收不依赖内存压力,只要发生GC且对象不可达,就会被立即回收(与软引用不同)。
- 虚引用(PhantomReference)
ini
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), queue);
- 创建一个空的引用队列
queue
。 - 创建一个虚引用
phantomRef
,指向一个新Object
实例,并注册到queue
中。 - 此时新
Object
实例仅被虚引用关联,可以马上被垃圾回收。 - 当对象被回收后,
phantomRef
会被JVM自动加入queue
。
引用类型 | 回收条件 | 典型场景 |
---|---|---|
软引用 | 内存不足时回收 | 缓存 |
弱引用 | 下次GC必定回收 | WeakHashMap、缓存 |
虚引用 | 随时回收,仅跟踪回收状态 | 堆外内存管理(如NIO) |
垃圾收集器的本质,是对对象生命周期的精密裁决。从强引用的绝对权威到虚引用的缥缈追踪,从栈帧的瞬时存灭到元空间的类卸载,JVM通过可达性规则构筑起一座隐形的'生死簿'。开发者与GC的协作,实则是主动资源管理与自动回收机制的博弈------唯有深刻理解'不可达即垃圾'这一铁律,才能在内存效率与代码健壮性之间找到平衡点。未来,随着ZGC、Shenandoah等低延迟回收器的演进,垃圾判定规则或将更加动态,但核心逻辑始终不变:内存世界的秩序,永远由可达性定义。