Java中的四种引用详解

Java中的四种引用

Java中有四种引用类型:强引用、软引用、弱引用、虚引用。

强引用

强引用是最常见的一种引用类型,在实际开发中,几乎都是强引用类型。

Object obj = new Object();
复制代码

当我们的对象有强引用的时候,即使内存不足,JVM宁愿抛出OOM,也不会把该对象回收掉。如果需要让垃圾回收器回收此对象,需要将强引用关系打破,最简单的方式就是将对象赋值为null。下面写个例子做个简单说明:

创建一个Order对象,为了更好的知晓垃圾回收的情况,这里重写finalize()方法,因为对象被垃圾回收时都会调用此方法。


/**
 * 订单对象
 */
public class Order {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("order对象被垃圾回收");
    }
}
复制代码

当对象的强引用关系被打破时,显式调用垃圾回收器

/**
 * 强引用类型垃圾回收
 */
public class NormalReferenceTest {
    public static void main(String[] args) throws IOException {
        Order order = new Order();
        // 破坏引用关系
        order = null;
        // 调用垃圾回收器,进行垃圾回收
        System.gc();
        System.out.println(order);
​
        // 阻塞Main主线程
        System.in.read();
​
    }
}
​
复制代码

输出结果:


null
order对象被垃圾回收
复制代码

根据输出结果,可知Order对象被垃圾回收掉了。

注意:
在生产环境上,一定不要去重写finalize()方法,有内存溢出的风险。由于每个对象在进行垃圾回收时都会去调用该对象的finalize()方法,该方法默认是空的,啥都没做,执行效率是非常快的。但是如果重写后就会影响垃圾回收的效率,假设此时业务上还有大量的对象产生,垃圾回收的效率小于对象产生的效率,时间一长就会内存溢出。

软引用


软引用就是用SoftReference将引用对象包装一下,通过get()方法获取包装的对象,使用方式如下:


SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024 * 1024 * 10]);
softReference.get();//获取软引用对象
复制代码

这里用一个例子说明软引用的特点

  1. 设置堆内存大小为20M,JVM参数-Xmx20M
  2. 首先通过SoftReference包装一个byte数组,大小为10M
  3. 第一次通过get()方法获取软引用对象
  4. 调用垃圾回收器
  5. 第二次通过get()方法获取软引用对象
  6. 再次new一个byte数组,大小为12M
  7. 第三次通过get()方法回去软引用对象

代码如下:


public class SoftReferenceTest {
    public static void main(String[] args) {
        SoftReference<byte[]> softReference = new SoftReference<>(new byte[1024 * 1024 * 10]);// 10M
        // 获取软引用对象
        System.out.println(softReference.get());
        // 调用垃圾回收
        System.gc();
        // 睡眠一下,给与垃圾回收时间
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 再次获取软引用对象
        System.out.println(softReference.get());
​
        // 再new一个字节数组,堆内存不够,会触发垃圾回收,如果内存空间还不够,就会把软引用引用的对象回收掉
        byte[] bytes = new byte[1024 * 1024 * 12];// 12M
        // 再次获取软引用对象,此时将会获取为null
        System.out.println(softReference.get());
        
    }
}
复制代码

运行结果:


[B@677327b6
[B@677327b6
null
复制代码

说明
第一次获取软引用对象是有值的,接着调用GC,GC垃圾回收后,第二次获取也是有值的,说明垃圾回收时先不会回收软引用对象。随后,继续往内存里面new一个12M的byte数组,显然,我们堆内存是20M,最开始的软引用对象占10M,这里在new 一个,内存是装不下的,所以触发了垃圾回收器回收10M的软引用对象,第三次获取软引用对象时为null,因为都已被垃圾回收器回收掉了。

软引用的特点:
当JVM进行垃圾回收时,如果第一次回收后内存足够,那么不会回收软引用对象;如果第一次回收后内存依旧不够,那么就会回收掉软引用对象。根据这个特点,软引用可以用来做缓存,当系统内存足够时,通过缓存获取值,如果内存不够时,先回收掉缓存,释放一定的内存空间,延迟OOM。

弱引用


弱引用就是通过WeakReference包装了一下,使用如下:


WeakReference<Order> weakReference = new WeakReference<>(new Order());
// 获取弱引用的值
System.out.println(weakReference.get());
复制代码

弱引用和软引用最大的区别就是无论内存是否足够,弱引用都会被GC回收


/**
 * 弱引用
 */
public class WeakReferenceTest {
    public static void main(String[] args) {
        WeakReference<Order> weakReference = new WeakReference<>(new Order());
        // 获取弱引用的值
        System.out.println(weakReference.get());
        // 垃圾回收
        System.gc();
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 获取弱引用的值
        System.out.println(weakReference.get());
    }
}
​
复制代码

运行结果:


com.test.soft.Order@677327b6
order对象被垃圾回收
null
复制代码

弱引用在WeakHashMapThreadLocal中有使用到。

虚引用

Phantom 虚幻的

  • 英[ˈfæntəm]

虚引用又叫做幻影引用,它需要配合一个队列来使用,但是我们无法通过虚引用来获取一个对象的真实引用,我们来看下面的代码


ReferenceQueue<Order> referenceQueue = new ReferenceQueue<>();
​
// 虚引用在垃圾回收的时候,会被放到一个队列里,这个队列供垃圾回收器做特殊处理
PhantomReference<Order> phantomReference = new PhantomReference<>(new Order(), referenceQueue);
System.out.println(phantomReference.get());// 这里输出结果为null
复制代码

上述代码第五行的输出结果居然为null,进入到源码里面会发现


public class PhantomReference<T> extends Reference<T> {
​
    /**
     * Returns this reference object's referent.  Because the referent of a
     * phantom reference is always inaccessible, this method always returns
     * <code>null</code>.
     *
     * @return  <code>null</code>
     */
    public T get() {
        return null;
    }
}
复制代码

这居然直接返回的null,虚引用不能通过get()方法获取到值,那到底有何存在的意义呢?
我们来看下面的代码:

  1. 为了演示效果,还是把堆内存大小设置为20M,添加JVM参数-Xmx20M
  2. new 一个ReferenceQueue队列,创建虚引用,将new Order(), referenceQueue作为参数传入
  3. 开启一个线程,不断的往list集合中放入数据,时间长了堆内存会满,触发垃圾回收
  4. 再开启一个线程,循环读取ReferenceQueue队列里面的值

/**
 * 虚引用
 */
public class PhantomReferenceTest {
​
    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        ReferenceQueue<Order> referenceQueue = new ReferenceQueue<>();
​
        // 虚引用在垃圾回收的时候,会被放到一个队列里,这个队列供垃圾回收器做特殊处理
        PhantomReference<Order> phantomReference = new PhantomReference<>(new Order(), referenceQueue);
        System.out.println(phantomReference.get());
​
        new Thread(() -> {
            while (true) {
                // 不断往list添加数据。内存会满,此时会进行垃圾回收,虚引用就会被放在队列里面
                list.add(new byte[1024 * 1024]);
                System.out.println(phantomReference.get());
            }
        }).start();
​
        new Thread(() -> {
            while (true){
                // 从队列里面读数据
                Reference<? extends Order> poll = referenceQueue.poll();
                if (poll != null){
                    System.out.println("虚引用对象被JVM回收" + poll);
                }
            }
        }).start();
​
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
    }
}
​
复制代码

输出结果:


null
null
null
null
null
null
null
null
null
null
null
null
null
order对象被垃圾回收
null
null
null
null
null
虚引用对象被JVM回收java.lang.ref.PhantomReference@12bc0828
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
    at com.test.soft.PhantomReferenceTest.lambda$main$0(PhantomReferenceTest.java:29)
    at com.test.soft.PhantomReferenceTest$$Lambda$1/1831932724.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)
​
复制代码

虚引用特点
当垃圾回收器准备回收一个对象,如果发现它还有虚引用,那么就会在回收该对象之前,把这个虚引用加入到与之关联的ReferenceQueue中。在NIO中,就用了虚引用来管理堆外内存。

猜你喜欢

转载自juejin.im/post/7130907513892896776