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中,就用了虚引用来管理堆外内存。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值