文章目录

  • 1 强引用
  • 2 软引用
  • 3 弱引用
  • 4 虚引用
  • 总结



在 Java 日常开发中,我们每时每刻都在使用“引用”,准确的说是普遍只使用强引用,但 Java 不仅仅只有强引用。

从 JDK 1.2 开始,Java 就提供了四种强度不同的引用,即强引用软引用弱引用虚引用。使用不同的引用,可以更灵活的操作内存,控制对象的生命周期。

1 强引用

强引用一般是指把一个对象赋给一个引用变量,是最普遍的引用。只要强引用存在,GC时就不会回收被引用的对象。如果内存空间不足,JVM 会抛出 OOM 异常。如果强引用对象不再使用了,需要弱化才能使其被GC回收。

public static void main(String[] args) throws InterruptedException {
  	Object a = new Object(); // 强引用
  	Object b = a; // 强引用
  	a = null; // 赋值为null,即弱化
  	System.gc();
    Thread.sleep(1000);
  	System.out.println(a);  // a为null
  	System.out.println(b);  // java.lang.Object@66a3ffec,b强引用a原来指向的对象,不被GC回收
}

2 软引用

软引用是一种相对强引用弱化了的引用,通过 java.lang.ref.SoftReference 类来实现,用来描述一些非必需的对象。在内存足够时,不会回收;内存不足时,会将这些对象列入回收范围,若回收后还是内存不足,才会抛出 OOM 异常。

// 设置JVM参数 -Xmx32m -Xms32m 便于强迫GC
public static void main(String[] args) throws InterruptedException {
  	SoftReference sr = new SoftReference(new byte[1024 * 1024 * 16]); // 16M的匿名对象,软引用
    System.out.println("gc前: " + sr.get()); // 对象存在
    System.gc();
    Thread.sleep(1000);
    System.out.println("gc后,强迫gc前: " + sr.get()); // 对象依然存在
    byte[] bytes = new byte[1024 * 1024 * 16]; // 创建16M的大对象,强迫GC
  	System.out.println("强迫gc后: " + sr.get()); // 对象为null,已被回收
}

软引用通常用在对内存敏感的程序中,如高速缓存等。内存够用就通过软引用取值,无需从繁忙的真实来源获取数据,以提升速度;内存不足就回收。Mybatis 中的缓存类 SoftCache 也用到了软引用:

public Object getObject(Object key) {
    Object result = null;
    SoftReference<Object> softReference = (SoftReference) this.delegate.getObject(key);
    if (softReference != null) {
        result = softReference.get();
        if (result == null) {
            this.delegate.removeObject(key);
        } else {
            synchronized (this.hardLinksToAvoidGarbageCollection) {
                this.hardLinksToAvoidGarbageCollection.addFirst(result);
                if (this.hardLinksToAvoidGarbageCollection.size() > this.numberOfHardLinks) {
                    this.hardLinksToAvoidGarbageCollection.removeLast();
                }
            }
        }
    }
    return result;
}

3 弱引用

弱引用的使用和软引用类似,通过 java.lang.ref.WeakReference 类来实现。弱引用的特点是不管内存是否足够,只要发生 GC,弱引用的对象都会被回收。

public static void main(String[] args) throws InterruptedException {
  	WeakReference<Object> wr = new WeakReference<Object>(new Object()); // 弱引用
  	System.out.println("gc前: " + wr.get());  // 对象存在
  	System.gc();
    Thread.sleep(1000);
  	System.out.println("gc后: " + wr.get());  // 对象为null
}

弱引用的应用:WeakHashMapThreadLocal

// 当key只有弱引用时,GC会清理键和值,WeakHashMap可作为简单的缓存表解决方案
public static void main(String[] args) throws InterruptedException {
    WeakHashMap<String, String> map = new WeakHashMap<String, String>();
    map.put(new String("key"), "value"); // 匿名对象key,弱引用
    System.out.println("gc前: " + map); // map有数据
    System.gc();
    Thread.sleep(1000);
    System.out.println("gc后: " + map); // map为空
}
// ThreadLocal.ThreadLocalMap.Entry继承了弱引用,key为当前线程实例,和WeakHashMap基本相同。
static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    // ......
}

4 虚引用

虚引用不能获取对象实例,因此又被称为幻象引用,通过 java.lang.ref.PhantomReference 类来实现。虚引用的唯一目的就是在对象被GC回收时收到一个系统通知,可以配合 ReferenceQueue 来记录被回收的对象。如果虚引用的对象被垃圾回收,Java虚拟机就会把这个引用加入其关联的引用队列中。

public static void main(String[] args) throws InterruptedException {
    ReferenceQueue<Object> queue = new ReferenceQueue<>(); // 引用队列
    PhantomReference<Object> pr = new PhantomReference<>(new Object(), queue); // 虚引用
    System.out.println(pr.get()); // null,虚引用不能获取对象
    System.out.println(queue.poll()); // null,引用队列为空
    System.gc();
    Thread.sleep(1000);
  	// 若引用队列为空,remove()方法会阻塞,等待引用被放入引用队列后,才继续执行;poll()不会阻塞
	// Reference<?> reference = queue.remove();
  	Reference<?> reference = queue.poll();
    System.out.println(reference); // java.lang.ref.PhantomReference@73ad2d6,GC后,对象被回收,虚引用进入队列
}

在 JDK 1.4 中新加入的 NIO 类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数,通过 Unsafe 类操作堆外内存(直接内存),在申请到一块直接内存后,会在Java 堆中分配一个 DirectByteBuffer 对象来保存这个直接内存的引用。这个 DirectByteBuffer 对象就用到了虚引用,当它被回收后,相应的用户线程会收到通知并对直接内存进行清理工作。这种方式避免了在 Java 堆和 Native 堆中来回复制数据,可以显著提高性能。

总结

引用类型

对象回收

生存时间

用途

强引用

从不

JVM停止前

对象的一般状态

软引用

内存不足时

内存不足前

对象缓存

弱引用

GC时

两次GC之间

对象缓存

虚引用

GC时

两次GC之间

管理堆外内存