Java 引用类型(强引用、软引用、弱引用和虚引用)


在 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之间管理堆外内存
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值