Java对象的强、软、弱、虚引用

自己一直对Java对象的强、软、弱、虚四种引用的概念不是很清楚,因此最近重新学习了一下。
乘此机会顺便用本篇博客总结一下阅读的相关资料,并记录一下自己的理解。

1、强引用(StrongReference)
强引用是最普遍被使用的引用。
通常情况下,我们创建一个对象时,使用的都是强引用,例如:

class Test {
    Test() {
        //在栈中创建变量object,其对堆中创建出的对象,就是强引用
        Object object = new Object();
        ...........
    }
}

如果一个对象具有强引用,那垃圾回收器绝不会回收它。
当内存空间不足时,JVM宁愿抛出OOM错误使程序异常终止,
也不会靠随意回收具有强引用的对象来解决内存不足的问题。

如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,
这样一来的话,JVM在合适的时间就会回收该对象。

2、软引用(SoftReference)
对于软引用,我参考的资料都是这么描述的:
“如果一个对象只具有软引用,那么在内存空间足够的时候,垃圾回收器就不会回收它;
一旦内存空间不足了,垃圾回收器就会回收这些对象的内存。
使用软引用关联的对象时,只要垃圾回收器没有回收它,该对象就可以被程序使用。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,
Java虚拟机就会把这个软引用加入到与之关联的引用队列中。”

然而,我自己测试了一下,发现JVM对软引用的实现并没有想象中的那么智能,还是依赖于GC机制的回收策略。
我自己写的测试代码如下:

public class Test {
    //这里是上文所述的引用队列,GC回收对象后,将对应引用加入到mRQ中
    private static ReferenceQueue<TestLargeObject> mRQ = new ReferenceQueue<>();

    public static void main(String[] args) {
        //创建一个List,以强引用的方式持有每个软引用对象
        List<SoftReference<TestLargeObject>> sl = new ArrayList<>();

        for (int i = 0; i < 10; ++i) {
            //创建软引用,持有大内存对象,并绑定mRQ
            SoftReference<TestLargeObject> tmp = new SoftReference<>(
                    new TestLargeObject("Soft " + i), mRQ);
            System.out.println("Just created " + tmp.get());
            //将软引用加入到sl中
            sl.add(tmp);

            //试图主动GC一下
            System.gc();

            //主线程休眠一下,给GC线程一点回收内存的时间
            try {
                Thread.sleep(3000);
            } catch (Exception e) {
                //......
            }

            //判断是否有引用加入到mRQ中
            checkQueue();
        }
    }

    //该函数就是完成打印功能
    private static void checkQueue() {
        Reference<? extends TestLargeObject> rq = mRQ.poll();
        if (rq != null) {
            System.out.println("In queue: " + rq);
        }
    }

    private static class TestLargeObject {
        private String mId;
        private double[] mLargeData;

        TestLargeObject(String id) {
            mId = id;
            //我的对象真的很大!!!!
            mLargeData = new double[50000000];
        }

        @Override
        public String toString() {
            return mId;
        }

        @Override
        public void finalize() {
            //析构时打印一下
            System.out.println("Finalizing ... " + mId);
            try {
                mLargeData = null;
                super.finalize();
            } catch (Throwable t) {
                //......
            }
        }
    }
}

该测试的实际运行结果如下:

Just created Soft 0
Just created Soft 1
Just created Soft 2
Just created Soft 3
Just created Soft 4
Just created Soft 5
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at Test$TestLargeObject.<init>(Test.java:42)
    at Test.main(Test.java:13)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
Finalizing ... Soft 1
Finalizing ... Soft 0
Finalizing ... Soft 5
Finalizing ... Soft 4
Finalizing ... Soft 3
Finalizing ... Soft 2

我的JDK是1.8.0_101,每次运行的结果可能略有差异,但无一例外会出现OOM。
如果改小TestLargeObject占用的内存,并增大创建的总数,依然会出现OOM。
有可能是自己的代码写的不太合理,不过单从这个实验现象来看,软引用或许在某些场景下也会出现问题。

3、弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。

在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。

弱引用同样可以和一个引用队列联合使用,
如果弱引用所引用的对象被垃圾回收,
Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

我们将上面代码中的软引用改为弱引用(对象名称修改为Weak),去掉线程休眠相关的代码(弱引用回收快,不需要休眠),
发现运行结果如下:

Just created Weak 0
Finalizing ... Weak 0
Just created Weak 1
In queue: java.lang.ref.WeakReference@1540e19d
Finalizing ... Weak 1
Just created Weak 2
In queue: java.lang.ref.WeakReference@677327b6
Finalizing ... Weak 2
Just created Weak 3
In queue: java.lang.ref.WeakReference@14ae5a5
Finalizing ... Weak 3
Just created Weak 4
In queue: java.lang.ref.WeakReference@7f31245a
Finalizing ... Weak 4
Just created Weak 5
In queue: java.lang.ref.WeakReference@6d6f6e28
Finalizing ... Weak 5
Just created Weak 6
In queue: java.lang.ref.WeakReference@135fbaa4
Finalizing ... Weak 6
Just created Weak 7
In queue: java.lang.ref.WeakReference@45ee12a7
Finalizing ... Weak 7
Just created Weak 8
In queue: java.lang.ref.WeakReference@330bedb4
Finalizing ... Weak 8
Just created Weak 9
In queue: java.lang.ref.WeakReference@2503dbd3
Finalizing ... Weak 9

每次运行的结果可能略微不同,但基本与上面的结果类似,不会出现OOM。
从上面的结果可以看出,弱引用还是比较靠谱的,其行为符合对应的介绍。
即GC线程扫描到仅有弱引用的对象时,就会直接释放对应内存,
然后将对应的引用加入到对应的ReferenceQueue中。

4、虚引用(PhantomReference)
最后,我们看看虚引用。

与其他几种引用都不同,虚引用并不会决定对象的生命周期。
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,
在任何时候都可能被垃圾回收器回收。

虚引用主要用来跟踪对象被垃圾回收器回收的活动。
虚引用与软引用、弱引用的一个区别在于:
虚引用必须和引用队列联合使用(软、弱都是可选的)。

同样使用上面的测试代码,将软引用改为虚引用,结果如下:

Just created null
Finalizing ... Phantom 0
Just created null
Finalizing ... Phantom 1
Just created null
Finalizing ... Phantom 2
In queue: java.lang.ref.PhantomReference@1540e19d
Just created null
In queue: java.lang.ref.PhantomReference@677327b6
Finalizing ... Phantom 3
Just created null
In queue: java.lang.ref.PhantomReference@14ae5a5
Finalizing ... Phantom 4
Just created null
In queue: java.lang.ref.PhantomReference@7f31245a
Finalizing ... Phantom 5
Just created null
In queue: java.lang.ref.PhantomReference@6d6f6e28
Finalizing ... Phantom 6
Just created null
In queue: java.lang.ref.PhantomReference@135fbaa4
Finalizing ... Phantom 7
Just created null
In queue: java.lang.ref.PhantomReference@45ee12a7
Finalizing ... Phantom 8
Just created null
In queue: java.lang.ref.PhantomReference@330bedb4
Finalizing ... Phantom 9

从上面的运行结果可以看出:
1、虚引用持有的对象,不能利用虚引用的Get来获取,将返回null。
毕竟对象仅被虚引用持有的话,随时可能被干掉,因此这么设计是合理的。

2、有些网上的资料写道,”对象被释放前,会先将引用先加入到ReferenceQueue中”。
从上面的结果来看,实际情况应该是:对象先被释放掉,才将其引用加入到ReferenceQueue中。
为了排除自己代码中System.gc和checkQueue调用顺序对结果的影响,
我把checkQueue函数移动到TestLargeObject的finalize函数中,依然发现对象是先析构,后添加引用到ReferenceQueue。
因此个人认为,”试图监控ReferenceQueue中的引用,在对象被回收前,采取必要行动”的说法不是很靠谱。

3、System.gc虽然不能保证GC线程立即执行工作,但还是有一定用处的。
如果在上述代码中移除对System.gc的调用,不论使用何种引用,无一例外全部OOM。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值