一、强引用
强引用
:只有所有 GC Root 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收。
- 强引用(Strong Reference)是Java中最为普遍的引用类型。当一个对象被强引用关联时,垃圾回收器不会回收这个对象,即使系统内存不足也不会回收。只有当该对象的强引用被显式地释放,或者不再被任何引用关联时,该对象才会成为垃圾回收的候选对象。
示例代码:
public class StrongReferenceExample {
public static void main(String[] args) {
// 创建一个对象并建立强引用
Object obj = new Object(); // 强引用
// 对象仍然存在,可以正常使用
System.out.println("Object is still accessible.");
// 解除对对象的强引用
obj = null;
// 系统内存充足时,垃圾回收器可能不会立即回收
// 只有在需要释放内存时,垃圾回收器才会回收不再被引用的对象
}
}
在这个例子中,创建了一个对象并建立了强引用。即使在解除对对象的强引用后,只要系统内存充足,垃圾回收器不会立即回收对象。强引用使得对象在被引用时一直保持有效,直到引用被显式解除。
强引用的特点:
- 假如说 GC Root 关联到了 User 对象,那就证明这个 User 对象是存活的,则这个 User 对象一直都不会被垃圾回收,即使出现了内存不足,抛出 OOM 异常,也不会回收强引用的对象。
- 只有当 GC Root 不再关联 User 对象,那这个对象才有可能会被垃圾回收器进行回收。
二、软引用
- 软引用用于描述一些还有用但并非必须的对象,在内存不足时可能被垃圾回收。
- 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收。
示例代码:
import java.lang.ref.SoftReference;
public class SoftReferenceExample {
public static void main(String[] args) {
// 创建一个大对象
byte[] largeObject = new byte[10 * 1024 * 1024]; // 10MB
// 使用软引用关联这个大对象
SoftReference<byte[]> softReference = new SoftReference<>(largeObject);
// 大对象不再被强引用,但仍然被软引用关联
// 尝试获取软引用关联的对象
byte[] retrievedObject = softReference.get();
if (retrievedObject != null) {
System.out.println("对象仍然存在,无需重新创建。");
} else {
System.out.println("对象已被回收,需要重新创建。");
}
}
}
在这个例子中,创建了一个10MB大小的大对象,并使用软引用SoftReference
与之关联。然后,尝试通过软引用获取对象。如果内存足够,对象就会保留;如果内存不足,垃圾回收器可能会回收该对象。这样,软引用允许在内存不足时释放一些非必要的对象,从而提高系统的内存利用率。
再来看一个例子:
User user = new User();
SoftReference softReference = new SoftReference(user);
在这段代码中,我们先创建了一个 User 对象,然后又创建了一个 SoftReference
对象并且包装了 User 对象。
首先 GC Root 能够关联到 SoftReference 对象,然后当前的 SoftReference 又会关联 User 对象。大家注意,SoftReference 对象和 User 对象之间的关联是使用的虚线,因为这种关联属于软引用。
在进行垃圾回收的时候,一开始并不会对 user 对象进行垃圾回收。由于 user 对象是一个软引用,如果在第一次垃圾回收之后内存还是不够,马上又进行了一次垃圾回收,这个时候软引用 User 对象就会被垃圾回收器回收了。
以上就是软引用的介绍,它必须配合 SoftReference 进行使用。
三、弱引用
- 弱引用(Weak Reference)是Java中一种比强引用更弱的引用类型。当一个对象只被弱引用关联时,在下一次垃圾回收时,该对象就有可能被回收。垃圾回收器会在适当的时候回收仅被弱引用持有的对象,即使内存并不紧张。
示例代码:
import java.lang.ref.WeakReference;
public class WeakReferenceExample {
public static void main(String[] args) {
// 创建一个对象并建立弱引用
Object obj = new Object();
WeakReference<Object> weakRef = new WeakReference<>(obj);
// 对象仍然存在,可以正常使用
System.out.println("Object is still accessible: " + weakRef.get());
// 解除对对象的强引用
obj = null;
// 手动触发垃圾回收
System.gc();
// 垃圾回收后,对象被回收,弱引用返回null
System.out.println("Object after garbage collection: " + weakRef.get());
}
}
在这个例子中,创建了一个对象并建立了弱引用。解除对对象的强引用后,手动触发垃圾回收。由于只有弱引用关联该对象,垃圾回收器可能会在垃圾回收时回收这个对象。因此,通过弱引用可以更容易地允许对象在合适的时候被回收。
再来看另一个例子:
User user = new User();
WeakReference weakReference = new WeakReference(user);
在这段代码中,先创建了一个 User 对象,然后创建了一个 WeakReference
对象并且包装了 User 对象。
首先,GC Root 关联到的是 WeakReference 对象,然后由 WeakReference 对象去关联了 User 对象,这里也是用虚线表示的。目前这个 User 对象就是一个弱引用。
在进行垃圾回收的时候。一旦内存不够用了,User 对象作为一个弱引用对象,就会被垃圾回收器回收掉。
关于弱引用有一个经典的例子,就是 ThreaLocal 内存泄露的问题:
在 ThreadLocal 中有一个 Entry 对象,它继承了 WeakReference,然后在构造函数里面调用了 super(k) 方法,也就表示了当前构造函数中的 ThreadLocal<?> 是一个弱引用,一旦内存不够的时候进行了垃圾回收,就会把 k 对象回收掉。但是 value 使用的是 = 进行赋值,就是一个强引用,并不会被垃圾回收器进行回收。所以说这块儿就可能产生内存泄漏。
四、虚引用
- 虚引用(Phantom Reference)是Java中最弱的引用类型之一,无法通过引用直接获取到对象实例。虚引用主要用于跟踪对象被垃圾回收的状态。当一个对象只被虚引用关联时,其实际上并不影响对象的生命周期,也就是说,垃圾回收器随时可能回收被虚引用关联的对象。
- 必须配合引用队列使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存。
示例代码:
User user = new User();
ReferenceQueue referenceQueue = new ReferenceQueue();
PhantomReference phantomReference = new PhantomReference(user, referenceQueue);
在这段代码中,先创建了一个 User 对象,然后创建了一个 ReferenceQueue
对象(就是一个引用队列),最后创建了一个 PhantomReference
对象并且包装了 User 对象和 ReferenceQueue 对象。
首先,GC Root 直接关联的是 PhantomReference 虚引用对象,一个是 X,另一个是 Y。这两个 PhantomReference 对象分别去引用了 User1 对象和 User2 对象。
大家可能会想,这里面哪里能体现 ReferenceQueue 队列呢?是这样的,将来如果发生了垃圾回收,把 User1 和 User2 这两个对象回收掉了,那么 PhantomReference 虚引用对象本身在进行垃圾回收发生的时候,会把虚引用对象 X 和 Y 加入到 ReferenceQueue 引用队列中,如下图所示:
把 X 和 Y 虚引用对象加入到 ReferenceQueue 引用队列中之后,引用队列就会配合 Reference Handler 这个线程来去释放虚引用对象所关联的一些外部资源。
比如说 User1 和 User2 已经被垃圾回收掉了,但是回收这两个对象只是释放了 Java 的堆内存资源,它们在使用的过程中有可能会使用一些外部的资源,这些外部资源有可能不是 Java 的内存,有可能使用的是系统的直接内存,那这些内存什么时候释放呢?这些内存必须要等 Java 对象回收掉之后,才能去释放这些外部的资源内存。所以说就需要把这些虚引用对象放入到引用队列中,先记录哪些对象被回收了,然后由 Reference Handler 根据队列的内容去回收资源就可以了。比如我们示例中的 X 和 Y 两个虚引用对象,它们关联的 User 对象已经被回收掉了,这个时候我们也应该把 X 和 Y 对应的外部资源进行释放,有一个专门的线程来进行释放,就叫 Reference Handler。它就会去从引用队列中不断地把这些虚引用对象 X 和 Y 取出来,然后把它们占用的外部资源进行释放。
看另外一个例子:
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class PhantomReferenceExample {
public static void main(String[] args) {
// 创建一个对象并建立虚引用
Object obj = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomRef = new PhantomReference<>(obj, referenceQueue);
// 对象实例不能通过虚引用直接获取
System.out.println("Object is not accessible: " + phantomRef.get());
// 解除对对象的强引用
obj = null;
// 手动触发垃圾回收
System.gc();
// 虚引用将被放入引用队列
if (referenceQueue.poll() != null) {
// 在引用队列中发现引用,表示对象已被垃圾回收
System.out.println("Object has been garbage collected.");
}
}
}
在这个例子中,创建了一个对象并建立了虚引用。由于虚引用无法通过get()方法获取对象实例,对象的生命周期不受虚引用的影响。解除对对象的强引用后,手动触发垃圾回收,虚引用将被放入引用队列。通过监测引用队列中是否有引用,可以了解对象是否已被垃圾回收。虚引用主要用于在对象被回收时执行一些清理操作。
五、总结
- 强引用:比较常见,只要 GC Root 能关联到,就不会被回收。
- 软引用:需要配合 SoftReference 使用,当垃圾被多次回收,内存依然不够的时候会回收软引用对象。
- 弱引用:需要配合 WeakReference 使用,只要进行了垃圾回收,就会把弱引用对象回收。
- 虚引用:必须配合引用队列使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存等外部资源。