JAVA中的引用类型,强引用软引用弱引用虚拟引用

强引用

两种都属于强引用,强引用在开发中最常用,只要存在对对象的引用就不会被GC回收

// 我们自己定义的实体通过直接new的形式都是强引用
Object obj = new Object();

软引用

一般对象不会被回收,当应用内存将要被耗尽的时候–>即将要发生OOM时,垃圾处理器就会把它回收,可以防止因OOM导致应用宕机不可用。

SoftReference<String> ref = new SoftReference<String>("aaa");

弱引用

当根引用失效时,下次触发GC时会被回收,回收完即便根引用重新赋值也不会重新建立引用

在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

WeakReference<String> ref = new WeakReference<String>("aaa");
示例
String str=new String("333");
WeakReference<String>ref=newWeakReference<>(str);
System.out.println(ref.get());
str=null;
System.out.println(ref.get());
System.gc(); // 手动触发一次gc
System.out.println(ref.get());

结果: 333 333 null

虚拟引用

虚拟引用非真实引用,不影响正常垃圾回收
当垃圾回收器决定对PhantomReference对象进行回收时,会将其插入ReferenceQueue中。

PhantomReference.class

public class PhantomReference<T> extends Reference<T> {
    
    
    public T get() {
    
    
        return null;
    }
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
    
    
        super(referent, q);
    }
}

对一个对象而言,这个引用形同虚设,有和没有一样。虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收过程,在对象被收集器回收时收到一个系统通知。 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在垃圾回收后,将这个虚引用加入引用队列,在其关联的虚引用出队前,不会彻底销毁该对象。

虚拟引用通常用于与引用队列ReferenceQueue同时使用

引用队列

ReferenceQueue

ReferenceQueue可以与SoftReference/WeakReference/PhantomReference配合使用

refrenceQueue本质上是一个链表,使用synchronized保证线程安全性,维护了volatile修饰的head对象,表示当前头部对象,并且维护了当前队列数量

看一下线程安全问题是如何保证的

static private class Lock {
    
     };
private Lock lock = new Lock();

可以看到此处使用了一个全局锁对象,这是个空对象,只单纯作为锁对象存在,其核心只是锁住了remove方法和enqueue方法(加入队列和移除队列)

synchronized(lock){
    
    
	// ...
}

当我们调用remove方法时会调用Object.wait方法阻塞,触发gc时enqueue会调用notifyAll通知线程,并非cas操作,因此对性能影响并不大

下面模拟一个场景,当我们对象被gc回收时,则移除map中的引用key

public class Test {
    
    

    private static ReferenceQueue<byte[]> referenceQueue = new ReferenceQueue<>();
    private static int _1M = 1024 * 1024;

    public static void main(String[] args) throws InterruptedException {
    
    
        final Map<Object, MybObject> map = new HashMap<>();
        Thread thread = new Thread(() -> {
    
    
            try {
    
    
                int n = 0;
                MybObject k;
                while (null != (k = (MybObject) referenceQueue.remove())) {
    
    
                    // 每次触发gc都会进来一次
                    System.out.println("gc:" + (++n));
                    map.remove(k.key);
                }
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        });
        thread.setDaemon(true);
        thread.start();
				// 创建1000个对象放入map中
        for (int i = 0; i < 1000; i++) {
    
    
            byte[] bytesKey = new byte[_1M];
            byte[] bytesValue = new byte[_1M];
            map.put(bytesKey, new MybObject(bytesKey, bytesValue, referenceQueue));
        }
        Thread.currentThread().join();
    }

    static class MybObject extends PhantomReference<byte[]> {
    
    
        private Object key;

        MybObject(Object key, byte[] referent, ReferenceQueue<? super byte[]> q) {
    
    
            super(referent, q);
            this.key = key;
        }
    }
}

运行结果

gc:1
gc:2
...
gc:792
gc:793
gc:794

可以看到这里回收到794就没有回收了,剩下206个对象还在堆内存中没有被释放

注: 该结果并非在所有电脑下都一样,和电脑,配置,jvm参数都有关,每次执行结果也不尽相同

Cleaner类

通过下图可以看到Cleaner继承PhantomRefrence虚拟引用类,其本质上也还是一个Refrence。

image-20220424103149881

ReferenceHandler会不停的从pending链表中取出引用对象,这个引用对象类型可能是任意一种,当引用对象为Cleaner时,直接调用Cleaner的clean方法就结束了。

使用方式也很简单,传入对象实例和runnable线程,当对象被回收时则会触发runnable的run方法

Cleaner.create(this, new Runnable() {
    
    
    @Override
    public void run() {
    
    

    }
});

开发中大部分情况下用的都是强引用,某些特殊的业务场景下,开发模式下也会用到其他引用类型,建议一定要仔细阅读本文,酌情使用

猜你喜欢

转载自blog.csdn.net/qq_21046665/article/details/124094802