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

在程序的世界里,内存如同城市的地基,承载着每一行代码的运转。而垃圾收集器(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等低延迟回收器的演进,垃圾判定规则或将更加动态,但核心逻辑始终不变:内存世界的秩序,永远由可达性定义。

相关推荐
Yuanymoon30 分钟前
【由技及道】API契约的量子纠缠术:响应封装的十一维通信协议【人工智障AI2077的开发日志012】
java·架构设计·spirng
lucky1_1star32 分钟前
FX-函数重载、重写(覆盖)、隐藏
java·c++·算法
李长渊哦1 小时前
学习文章:Spring Boot 中如何使用 `@Async` 实现异步处理
java·spring boot·学习
奔跑的废柴4 小时前
LeetCode 738. 单调递增的数字 java题解
java·算法·leetcode
霸王龙的小胳膊6 小时前
SpringMVC-请求和响应
java·mvc
二两小咸鱼儿7 小时前
Java Demo - JUnit :Unit Test(Assert Methods)
java·后端·junit
字节源流7 小时前
【spring】配置类和整合Junit
java·后端·spring
跪在镜子前喊帅8 小时前
【面试】Java 多线程
java·面试
好看资源平台8 小时前
Java/Kotlin逆向基础与Smali语法精解
java·开发语言·kotlin
zimoyin8 小时前
解决 Java/Kotlin 资源加载问题
java·python·kotlin