✅什么是强引用、软引用、弱引用和虚引用?

在Java中,强引用、软引用、弱引用和虚引用是用于管理对象生命周期的不同类型的引用。它们的主要作用是帮助垃圾回收器(GC)决定何时回收对象,从而更高效地管理内存。

强软弱虚,按照这个顺序,对象的引用的强度越来越弱。

强引用

强引用是Java的默认引用形式,使用时不需要显示定义。如果一个对象具有强引用,那垃圾回收器绝不会回收它(可达时)。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。

String[] arr = new String[]{"a", "b", "c"};

案例

强引用是默认的引用方式,我们创建的new 一个对象,就是用的强引用,即如果引用一直在,就不会被 GC 回收掉。

在集合中, 如我们常见的HashMap使用的引用就是强引用,也就是说垃圾收集的时候,Map中引用的对象不会被GC掉。

软引用

软引用是一种相对较弱的引用类型,用于表示对对象的“非强”引用。

软引用是使用SoftReference来创建对象的方式,如SoftReference<Object> softRef = new SoftReference<>(new Object());就是软引用。

当内存不足时,垃圾回收器会优先回收软引用指向的对象,以释放内存。软引用可以在系统内存充足时保留对象,以提高性能(缓存机制)。

SoftReference<String[]> softBean = new SoftReference<String[]>(new String[]{"a", "b", "c"});

案例

在 Guava Cache 中,有关于软引用的使用:

class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
  static class SoftValueReference<K, V> extends SoftReference<V> implements ValueReference<K, V> {
      final ReferenceEntry<K, V> entry;

      SoftValueReference(ReferenceQueue<V> queue, V referent, ReferenceEntry<K, V> entry) {
          super(referent, queue);
          this.entry = entry;
      }
  }
}

LocalCache 类用到了 SoftReference 来实现软引用缓存。通过软引用,来方便在内存不足时能自动回收缓存对象,来避免 OOM 的发生。

弱引用

弱引用比软引用更弱。被弱引用指向的对象只能存活到下一次垃圾回收之前。弱引用是使用WeakReference来创建对象的方式,WeakReference<Object> weakRef = new WeakReference<>(new Object());

WeakReference<Object> weakBean = new WeakReference<Object>(new Object());

如果一个对象只有弱引用指向,即使系统内存充足,垃圾回收器也会立即回收它。

案例

JDK中有一种基于弱引用类型实现的HashMap——WeakHashMap。

public class WeakHashMap<K, V> extends AbstractMap<K, V> implements Map<K, V> {
  //省略部分方法和属性
  private static class Entry<K, V> extends WeakReference<Object> implements Map.Entry<K, V> {
        V value;
        final int hash;
        Entry<K, V> next;

        Entry(Object var1, V var2, ReferenceQueue<Object> var3, int var4, Entry<K, V> var5) {
            super(var1, var3);
            this.value = var2;
            this.hash = var4;
            this.next = var5;
        }
  }
}

这里面的Entry 就是一种WeakReference,即弱引用,也就是说,当WeakHashMap中的 key已经不再被使用的时候,Entry 因为是弱引用,那么就会被自动回收掉。

另外,为了避免内存泄漏,在 ThreadLocal 中也用到了弱引用:

✅ThreadLocal为什么会导致内存泄漏?如何解决的?

虚引用

虚引用是最弱的一种引用。虚引用的存在不会影响对象的生命周期,垃圾回收器在回收对象时不会考虑虚引用。虚引用是使用PhantomReference来创建对象的方式,

PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), referenceQueue);

虚引用主要用于跟踪对象被回收的状态,并在对象被回收后进行一些后续处理(如清理本地资源等)。

虚引用通常与 ReferenceQueue 结合使用,管理清理逻辑。它在很多需要资源清理的场景中使用,比如文件句柄、数据库连接等。

import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.nio.ByteBuffer;

public class PhantomReferenceExample {

    // 定义一个简单的资源管理类
    static class ResourceCleaner {
        private final ReferenceQueue<ByteBuffer> queue;
        private final PhantomReference<ByteBuffer> ref;

        public ResourceCleaner(ByteBuffer buffer) {
            // 创建一个 ReferenceQueue
            this.queue = new ReferenceQueue<>();
            // 创建一个虚引用并与 ReferenceQueue 关联
            this.ref = new PhantomReference<>(buffer, queue);
        }

        public void cleanUp() {
            // 在某个时间点调用清理方法
            PhantomReference<ByteBuffer> polledRef = (PhantomReference<ByteBuffer>) queue.poll();
            if (polledRef != null) {
                // 进行资源释放操作
                System.out.println("Cleaning up resources...");
                // 释放与 ByteBuffer 相关的资源
                // 在这里可以进行额外的清理工作,如关闭文件句柄等
            }
        }
    }

    public static void main(String[] args) {
        // 创建一个直接内存缓冲区
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

        // 创建 ResourceCleaner 并传入缓冲区
        ResourceCleaner cleaner = new ResourceCleaner(buffer);

        // 断开对缓冲区的强引用
        buffer = null;

        // 手动触发垃圾回收(为了示例,实际使用中不建议如此操作)
        System.gc();

        // 调用清理方法,检查是否有虚引用已被回收并进行清理
        cleaner.cleanUp();
    }
}

对比

特性 强引用 软引用 弱引用 虚引用
生命周期 最长 次于强引用 次于软引用 次于弱引用
OOM前被清理
GC前被清理

原文: https://www.yuque.com/hollis666/xkm7k3/mx9eo0s2s5iaah2s