Java对象的强、软、弱、虚引用
来源:互联网 发布:淘宝运营绩效考核方案 编辑:程序博客网 时间:2024/06/05 05:40
自己一直对Java对象的强、软、弱、虚四种引用的概念不是很清楚,因此最近重新学习了一下。
乘此机会顺便用本篇博客总结一下阅读的相关资料,并记录一下自己的理解。
1、强引用(StrongReference)
强引用是最普遍被使用的引用。
通常情况下,我们创建一个对象时,使用的都是强引用,例如:
class Test { Test() { //在栈中创建变量object,其对堆中创建出的对象,就是强引用 Object object = new Object(); ........... }}
如果一个对象具有强引用,那垃圾回收器绝不会回收它。
当内存空间不足时,JVM宁愿抛出OOM错误使程序异常终止,
也不会靠随意回收具有强引用的对象来解决内存不足的问题。
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 0Just created Soft 1Just created Soft 2Just created Soft 3Just created Soft 4Just created Soft 5Exception 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 1Finalizing ... Soft 0Finalizing ... Soft 5Finalizing ... Soft 4Finalizing ... Soft 3Finalizing ... Soft 2
我的JDK是1.8.0_101,每次运行的结果可能略有差异,但无一例外会出现OOM。
如果改小TestLargeObject占用的内存,并增大创建的总数,依然会出现OOM。
有可能是自己的代码写的不太合理,不过单从这个实验现象来看,软引用或许在某些场景下也会出现问题。
3、弱引用(WeakReference)
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。
在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。
弱引用同样可以和一个引用队列联合使用,
如果弱引用所引用的对象被垃圾回收,
Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
我们将上面代码中的软引用改为弱引用(对象名称修改为Weak),去掉线程休眠相关的代码(弱引用回收快,不需要休眠),
发现运行结果如下:
Just created Weak 0Finalizing ... Weak 0Just created Weak 1In queue: java.lang.ref.WeakReference@1540e19dFinalizing ... Weak 1Just created Weak 2In queue: java.lang.ref.WeakReference@677327b6Finalizing ... Weak 2Just created Weak 3In queue: java.lang.ref.WeakReference@14ae5a5Finalizing ... Weak 3Just created Weak 4In queue: java.lang.ref.WeakReference@7f31245aFinalizing ... Weak 4Just created Weak 5In queue: java.lang.ref.WeakReference@6d6f6e28Finalizing ... Weak 5Just created Weak 6In queue: java.lang.ref.WeakReference@135fbaa4Finalizing ... Weak 6Just created Weak 7In queue: java.lang.ref.WeakReference@45ee12a7Finalizing ... Weak 7Just created Weak 8In queue: java.lang.ref.WeakReference@330bedb4Finalizing ... Weak 8Just created Weak 9In queue: java.lang.ref.WeakReference@2503dbd3Finalizing ... Weak 9
每次运行的结果可能略微不同,但基本与上面的结果类似,不会出现OOM。
从上面的结果可以看出,弱引用还是比较靠谱的,其行为符合对应的介绍。
即GC线程扫描到仅有弱引用的对象时,就会直接释放对应内存,
然后将对应的引用加入到对应的ReferenceQueue中。
4、虚引用(PhantomReference)
最后,我们看看虚引用。
与其他几种引用都不同,虚引用并不会决定对象的生命周期。
如果一个对象仅持有虚引用,那么它就和没有任何引用一样,
在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。
虚引用与软引用、弱引用的一个区别在于:
虚引用必须和引用队列联合使用(软、弱都是可选的)。
同样使用上面的测试代码,将软引用改为虚引用,结果如下:
Just created nullFinalizing ... Phantom 0Just created nullFinalizing ... Phantom 1Just created nullFinalizing ... Phantom 2In queue: java.lang.ref.PhantomReference@1540e19dJust created nullIn queue: java.lang.ref.PhantomReference@677327b6Finalizing ... Phantom 3Just created nullIn queue: java.lang.ref.PhantomReference@14ae5a5Finalizing ... Phantom 4Just created nullIn queue: java.lang.ref.PhantomReference@7f31245aFinalizing ... Phantom 5Just created nullIn queue: java.lang.ref.PhantomReference@6d6f6e28Finalizing ... Phantom 6Just created nullIn queue: java.lang.ref.PhantomReference@135fbaa4Finalizing ... Phantom 7Just created nullIn queue: java.lang.ref.PhantomReference@45ee12a7Finalizing ... Phantom 8Just created nullIn queue: java.lang.ref.PhantomReference@330bedb4Finalizing ... Phantom 9
从上面的运行结果可以看出:
1、虚引用持有的对象,不能利用虚引用的Get来获取,将返回null。
毕竟对象仅被虚引用持有的话,随时可能被干掉,因此这么设计是合理的。
2、有些网上的资料写道,”对象被释放前,会先将引用先加入到ReferenceQueue中”。
从上面的结果来看,实际情况应该是:对象先被释放掉,才将其引用加入到ReferenceQueue中。
为了排除自己代码中System.gc和checkQueue调用顺序对结果的影响,
我把checkQueue函数移动到TestLargeObject的finalize函数中,依然发现对象是先析构,后添加引用到ReferenceQueue。
因此个人认为,”试图监控ReferenceQueue中的引用,在对象被回收前,采取必要行动”的说法不是很靠谱。
3、System.gc虽然不能保证GC线程立即执行工作,但还是有一定用处的。
如果在上述代码中移除对System.gc的调用,不论使用何种引用,无一例外全部OOM。
- java中对象的引用(强引用、软引用、弱引用、虚引用)
- Java对象的引用方式(强引用, 软引用, 弱引用, 虚引用)
- java对象的强引用,软引用,弱引用…
- JAVA对象的强,弱,虚引用
- Java:Java对象的强、软、弱和虚引用
- 【Java进阶】Java对象的强、软、弱、虚引用
- Java中对于对象的强引用、软引用、弱引用以及虚引用的区别
- java对象的强引用,软引用,弱引用和虚引用
- Java对象的强引用、软引用、弱引用和虚引用
- java对象的强引用,软引用,弱引用和虚引用
- java对象的强引用,软引用,弱引用和虚引用
- java对象的强引用,软引用,弱引用和虚引用
- java对象的强引用,软引用,弱引用和虚引用
- java对象的强引用,软引用,弱引用和虚引用
- java对象的强引用,软引用,弱引用和虚引用
- java对象的强引用,软引用,弱引用和虚引用
- java对象的强引用,软引用,弱引用和虚引用
- java对象的强引用,软引用,弱引用和虚引用
- XML解析错误,Error Domain=NSXMLParserErrorDomain Code=9 "(null)"
- 表达式语言 EL (Expression Language)
- AngularJs[自定义过滤器]
- PDF 合并 切割 神器
- 深入理解JVM
- Java对象的强、软、弱、虚引用
- 学习JAVA过程:构造函数
- 1.桶排序——啊哈算法java实现
- Shell 表达式判断
- 如何从流程表单录入抛单到组织机构
- 大数加法
- centos 6.4部署e + k 使用supervisord引爆
- 解决“Linux无法登录,显示module is unknown”问题
- UE4 使用vive手柄的方法