垃圾收集器收集的垃圾是什么?

在程序的世界里,内存如同城市的地基,承载着每一行代码的运转。而垃圾收集器(Garbage Collector, GC)则是这座城市的清洁系统,它日夜巡视,回收那些不再被需要的"建筑残骸"------即程序运行中产生的垃圾对象。但究竟什么样的数据会被判定为"垃圾"?是我们主动丢弃的变量,还是某种隐秘的规则在幕后裁决?

一、显示产生

开发者主动操作导致对象不可达

  1. 置空引用
ini 复制代码
 Object obj = new Object();
 obj = null;

局部变量obj是个引用,存放在当前方法的栈帧的局部变量表中。

new Object()产生的对象实例放在堆中。

当把obj置空后,没有其他引用指向堆中的对象实例,这个对象实例就变成了垃圾。

obj置空了,那obj本身是不是也变成了垃圾?需要GC清理吗?

确实是垃圾,但是不需要GC的参与。栈帧从虚拟机栈弹出,内存立即释放。

  1. 局部变量超出作用域(方法级)
javascript 复制代码
 void doSomething() {
     Object obj = new Object();
 }

方法结束后,栈帧中的局部变量(如 obj)被自动释放,堆中的对象失去唯一强引用,变成垃圾。

  1. 局部变量超出作用域(代码块级)
ini 复制代码
 void doSomething() {
     for (int i = 0; i < 10; i++) {
         Object obj = new Object();
     }
 }

这里的i和obj在编译期确定在局部变量表中的槽位(Slot)索引。

每次进入循环,堆上都产生一个对象实例,而对应槽位上的值(引用)会覆盖修改,指向新的对象实例。

下一次循环会导致上一次循环产生的实例对象的应用被断开(因为指向了新的),形成垃圾。

最后一次产生的对象因为方法的结束,栈帧被弹出,引用消失(释放),失去了引用,变成垃圾。

二、隐式产生

对象因逻辑操作(如集合操作、静态变量变更)失去引用,但开发者未显式干预。

  1. 集合清空或者移除元素
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(无指向),此时堆内存中的对象变成了垃圾。

  1. 静态变量重新赋值
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();产生的新对象实例。老对象实例失去引用,变成垃圾。

三、动态产生

  1. 类卸载
ini 复制代码
 ClassLoader loader = new CustomClassLoader();
 Class<?> clazz = loader.loadClass("MyClass");
 loader = null;

loader变量强引用CustomClassLoader实例。

loader = null断开了强引用,但需检查是否有其他引用:

  • 隐式引用 :JVM在加载类时,MyClassClass 对象会隐式引用其 ClassLoader
  • 显式引用 :若 clazz 变量仍持有 MyClassClass 对象,则 ClassLoader 仍被隐式引用。

四、特定场景产生

  1. 软引用(SoftReference)
javascript 复制代码
 SoftReference<Object> softRef = new SoftReference<>(new Object());
  • 创建了一个Object实例(强引用指向它)。
  • 将该对象包装到SoftReference中,此时对象仅通过软引用可达。
  • 代码执行后,原强引用(new Object()的临时引用)消失,对象仅由软引用softRef关联。
  • 内存不足时,尝试回收仅被软引用关联的对象(被当成垃圾)。
  1. 弱引用(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且对象不可达,就会被立即回收(与软引用不同)。
  1. 虚引用(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等低延迟回收器的演进,垃圾判定规则或将更加动态,但核心逻辑始终不变:内存世界的秩序,永远由可达性定义。

相关推荐
DokiDoki之父3 分钟前
Spring—注解开发
java·后端·spring
CodeCraft Studio36 分钟前
【能源与流程工业案例】KBC借助TeeChart 打造工业级数据可视化平台
java·信息可视化·.net·能源·teechart·工业可视化·工业图表
摇滚侠43 分钟前
Spring Boot 3零基础教程,WEB 开发 默认页签图标 Favicon 笔记29
java·spring boot·笔记
YSRM1 小时前
Leetcode+Java+图论+最小生成树&拓扑排序
java·leetcode·图论
沐浴露z2 小时前
【JVM】详解 Class类文件的结构
java·jvm·class
桦说编程2 小时前
Java并发编程:两种控制并发度的实现方法及其比较
java·后端
杯莫停丶2 小时前
设计模式之:单例模式
java·单例模式·设计模式
消失的旧时光-19432 小时前
@JvmStatic 的作用
java·开发语言·kotlin
火锅机器2 小时前
java 8 lambda表达式对list进行分组
java·开发语言·list