Java中的四种引用详解(强引用、软引用、弱引用、虚引用)

强引用 软引用 弱引用 虚引用

前言

在平常的开发中用到的几乎都是强引用,但是面试时会问到的却都是其他三种不常用的引用。

通用内部测试类,测试运行时添加限制堆内存大小参数:-Xmx200m

static class Vince {
    // 创建100M大小的字节数组
    private byte[] bytes = new byte[1024*1024*100];

}

1、强引用

对于一个对象,如果有强引用指向它,那么它不会被垃圾回收掉,就算内存不够用了,也不会被回收掉,只会抛出OOM异常。

测试方法

private static void strongReference() {
    Vince vince = new Vince();
    Vince vince1 = new Vince();
}

依次创建两个强引用对象,在创建第一个对象时,消耗100M内存,创建第二个对象,还需要100M内存,此时内存不足,抛出OOM异常。

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.patent.demo.reference.ReferenceTest$Vince.<init>(ReferenceTest.java:112)
	at com.patent.demo.reference.ReferenceTest.strongReference(ReferenceTest.java:40)
	at com.patent.demo.reference.ReferenceTest.main(ReferenceTest.java:22)

2、软引用

对于一个对象,如果有软引用指向它,内存足够时,垃圾回收不会回收掉它,但是当内部不足时,垃圾回收会回收掉软引用对象。

测试方法

(1)、先创建强引用对象,再创建软引用对象时,内存不足以创建软引用对象,会抛出OOM异常;

private static void softReference() {
    Vince vince = new Vince();
    SoftReference<Vince> reference = new SoftReference<Vince>(new Vince());
    System.out.println(reference.get());
}

(2)、先创建软引用对象,再创建强引用对象时,内存不足时,软引用对象会被垃圾回收;

private static void softReference_1() {
    SoftReference<Vince> reference = new SoftReference<Vince>(new Vince());
    Vince vince = new Vince();
    System.out.println(reference.get()); // null 被回收了
}

3、弱引用

对于一个对象,如果有弱引用指向它,不管内存够不够用,在垃圾回收的时候就会被回收掉;

测试方法

private static void weakReference() {
    WeakReference<Vince> reference = new WeakReference<>(new Vince());
    System.out.println(reference.get()); // com.patent.demo.reference.ReferenceTest$Vince@2328c243
    System.gc();
    System.out.println(reference.get()); // null 被回收了
}

4、虚引用

虚引用和其他三种引用的使用方法都不一样,先介绍使用方法,以下是使用方法:

private static void phantomReference() throws InterruptedException {
    // 引用队列
    ReferenceQueue<Vince> referenceQueue = new ReferenceQueue<>();
    PhantomReference<Vince> reference = new PhantomReference<>(new Vince(), referenceQueue);
    System.out.println(reference.get()); // null
}

可以看到,相比于软引用和弱引用,虚引用多了一个 ReferenceQueue (引用队列),虚引用要搭配 ReferenceQueue 使用;执行该方法时,最终输出的内容为null,因为虚引用实在是太虚了,无法通过这种方式获取到虚引用的值。

虚引用的作用是什么呢?

当垃圾回收时,虚引用会被回收掉,同时虚引用会被放进引用队列中,因此可以用虚引用来记录哪些对象被垃圾回收。

测试方法

private static void phantomReference() throws InterruptedException {
    ReferenceQueue<Vince> referenceQueue = new ReferenceQueue<>();
    PhantomReference<Vince> reference = new PhantomReference<>(new Vince(), referenceQueue);
    System.out.println(reference.get()); // null
    System.out.println(referenceQueue.poll()); // null
    System.gc();
    Thread.sleep(1000);
    System.out.println(referenceQueue.poll()); // java.lang.ref.PhantomReference@2328c243
}

NIO 经典使用场景

虚引用比较经典的使用场景是NIO的 ByteBuffer,模拟下具体场景:

private static void phantomReference_1() {
    // 分配直接内存
    ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024 * 100);
    // 置空,方便垃圾回收
    buffer = null;
    // 垃圾回收后,直接内存也被释放了
    System.gc();
}

执行该方法,最后可以发现,垃圾回收之后,直接内存也被释放了,怎么做到的呢?

实际上,在 allocateDirect 方法内部,使用了 Cleaner 类,这个类是 JDK 提供的,它继承了 PhantomReference 类,即虚引用。

allocateDirect 方法源码

// ByteBuffer 类
public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}

// DirectByteBuffer 类
DirectByteBuffer(int cap) {                   // package-private
    super(-1, 0, cap, cap);
    boolean pa = VM.isDirectMemoryPageAligned();
    int ps = Bits.pageSize();
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    Bits.reserveMemory(size, cap);

    long base = 0;
    try {
        base = unsafe.allocateMemory(size);
    } catch (OutOfMemoryError x) {
        Bits.unreserveMemory(size, cap);
        throw x;
    }
    unsafe.setMemory(base, size, (byte) 0);
    if (pa && (base % ps != 0)) {
        // Round up to page boundary
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    // 使用了 Cleaner 类
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;
}

// Deallocator 类中的run方法
public void run() {
    if (address == 0) {
        // Paranoia
        return;
    }
    // 释放直接内存
    unsafe.freeMemory(address);
    address = 0;
    Bits.unreserveMemory(size, capacity);
}

Cleaner 类,我们自己也可以这么使用:

private static void phantomReference_2() {
    Vince vince = new Vince();
    Cleaner cleaner = Cleaner.create(vince, new Runnable() {
        @Override
        public void run() {
            System.out.println("123456");
        }
    });
    vince = null;
    System.gc();
    System.out.println("789789");
}

利用Cleaner生成了一个虚引用指向Vince对象,然后通过gc把Vince对象回收掉,但在真正回收之前,会先调用Cleaner中设置的Runnable对象的run方法,相当于一种回调。

而在ByteBuffer中,就是利用了这种机制。对于ByteBuffer对象,有一个强引用指向它,同时也有一个Cleaner虚引用指向它,同时指定Deallocator对象作为回调,Deallocator中的run方法会进行释放直接内存,只要把强引用ByteBuffer置空,就会进行垃圾回收,并执行Deallocator中的run方法释放直接内存。

总结

  • 1、强引用,不会被垃圾回收;
  • 2、软引用,当内存不足时,会被垃圾回收;
  • 3、弱引用,当执行垃圾回收时,就会被直接回收掉;
  • 4、虚引用,要搭配ReferenceQueue使用,可以用来记录某一对象是否被垃圾回收了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值