天天看点

Java reference详解

最近在做一个服务编排执行引擎的东西,其中业务逻辑执行的参数上下文存储在了ThreadLocal里面,为了保证运行参数不丢失,对ThreadLocal进入了一些源码研究,发现实际的存储是在ThreadLocalMap里面,而map的中key居然是weakreference类型,这能保证GC的时候,业务运行参数不丢失吗?由此,对不同的引用reference做了一下深入了解,机制是什么?应用的场景是什么?

概述:

  • reference的目的是为了让程序能够对JVM的垃圾回收施加一定影响,或者说一种交互机制,且这种影响是有限的。
  • 每种reference对象都继承自Reference类,其有一个referent属性,用于存储被引用对象。GC在做垃圾回收的时候,回收的就是这个referent对象。
  • 通过 reference的get方法获取真正的被引用对象。
  •  一个对象只有失去所有强引用以后,才有可能根据不同引用类型执行不同的回收策略。

引用类型主要分为四种:

  • Strong reference:

String strRefer = new String("referece");

GC在标记阶段,从root set出发,被遍历到的强引用对象不会被垃圾回收。

  • soft reference:

String strRefert = new String("referece");

SoftReference<String> softRef = new SoftReference<String>(strRefert);

strRefert = null;

System.gc();

System.out.printlin("is garbage collect ========" + softRef.get());

对于软引用,GC有一套自己的策略,只要没有发生out of space,GC就有可能不回收软引用对象。

  • weak reference:

String strRefert = new String("referece");

WeakReference<String> weakRef = new WeakReference<String>(strRefert);

strRefert = null;

System.gc();

System.out.printlin("is garbage collect ========" + weakRef.get());

对于弱引用,只要发生GC,引用对象就会被垃圾回收

  • phantom reference:

行为与weakreference一致,区别在于生成phantom时,必须传入referenceQuene对象。GC回收对象时,如果引用类型是weakreference类型,会在执行被引用对象前的finalize前,将weakreference对象推入referenceQuene;而如果是phantomreference类型,则是先执行被引用对象的finalize,然后再将phantomreference对象推入referenceQuene。phantomreference的应用场景很少。

那么referenceQuene又是什么鬼?

以上面的代码为例,GC做了垃圾回收动作,那么程序怎么知道referent到底有没有被回收呢?一个一个对象的去遍历,是不是low了点儿?referenceQuene帮我们做了统一的收口,如果我们在创建弱引用对象的时候,传入了referenceQuene参数:

String strRefert = new String("referece");

ReferenceQuene<String> refQuene = new ReferenceQuene<String>();

WeakReference<String> weakRef = new WeakReference<String>(strRefer, refQuene);

strRefert = null;

System.gc();

System.out.printlin("is garbage collect ========" + weakRef.get());

System.out.printlin("is enquened ========" + weakRef.isEnqueued());//是否是已经被入列

System.out.printlin("weakRef ========" + refQuene.pull());

System.out.printlin("is same instance ========" + (weakRef == refQuene.pull()));

System.out.printlin("weakRef ========" + refQuene.remove());

那么GC做完垃圾回收以后,会将reference对象push到ReferenceQuene里面,这样我们知道reference引用的referent被垃圾回收了,也表明此时的reference对象也没有任何意义了,需要我们对其进行清理,或者做一些其他的资源清理工作。

ReferenceQuene主要有两个方法:

poll():如果存在reference对象,则拉取并从队列中删除,如果没有则返回null,不阻塞

remove():阻塞方法,直到有reference对象为止。

说了半天,应用场景跟目的到底是啥:

看了很多篇文章,大部分都说用作缓存,Map的方式,用weakReference将key进行一次包裹,作为map的key,那么当key失去强引用以后,GC会对weakReference包裹的key进行回收清除,这样value也就可以被清除了;很多人对此提出了质疑,key都没有了,还怎么进行下次查询,缓存的意义何在?我也深以为然!所以,个人认为,还是为了内存不会出现out of space。oracle的官网举了个列子,就是我们浏览大量的图片,还翻页,还不知道会不会往回翻页,图片很大,加载很慢,这时我们可以考虑用弱引用,往回翻页,存在正好,不存在了则再重新加载一遍。问题又来了,图片的缓存肯定会以图片名称作为key,图片的实际内容作为value,这会是什么样的一种结构?

weakhashmap描述:

如上所述的map,Java给我们提供了WeakHashMap类,其key值就是被weakReference所包裹的可以被GC回收的,问题又来了,图片的名称是key,但我们实际上要回收的是value,真正的图片内容,且WeakHashMap宣称是自动回收的,这个是怎么实现的呢?通过研究源码发现原来在对WeakHashMap做任何操作的时候,put、get、size、toString等等,都会首先调用一个expungeStaleEntries方法,仔细分析其源码,原来这个方法会pull对应的referenceQuenue,把已经清除的key找出来,然后再删除对应的value值,其所宣贯的自动清除,实际上是在调用它的方法时触发的。那么ThreadLocalMap又怎么保证虽然weakReference作为key,但执行过程中不被清除的呢?

ThreadLocalMap实现:

看注解:

根本就没有清除value!

参考文献:

http://www.fis.unipr.it/lca/tutorial/java/refobjs/index.html

http://learningviacode.blogspot.com/2014/02/reference-queues.html

https://community.oracle.com/blogs/enicholas/2006/05/04/understanding-weak-references

https://stackoverflow.com/questions/7136620/weak-references-how-useful-are-they

https://stackoverflow.com/questions/5511279/what-is-a-weakhashmap-and-when-to-use-it

https://docs.oracle.com/javase/6/docs/api/java/lang/ref/package-summary.html#reachability