java gc --- 四种引用
古龙有《七种武器》,java里有四种引用。
下文主要是对 《understanding-weak-references》这一博文的重点进行翻译
强引用,strong references
StringBuffer buffer = new StringBuffer()
像上面这样的就是强引用,只要某个对象与gc root有强引用链相连,那么这个对象就不可能被gc回收掉。jvm宁可发生OOM,也不会回收这个对象。
强引用有时可能太强了
作者提出了两种场景
1. 假设我们有一个不可扩展的Widget类,现在我们想给每个Widget对象附加一个编号属性,那么我们可以这么做:额外搞一个HashMap,key是Widget对象,value是编号,通过更新与查询这个HashMap,可以实现想要的功能。
但是这个时候问题来了,如果有的Widget对象不再被使用到了(成为了垃圾),由于HashMap中保存了对所有Widget的强引用,所以GC绝对不会释放这些Widget,那么这个HashMap就会越来越大,与之关联的Widget也会越来越多,直到发生OOM。
为了解决这个问题,一个很简单的想法当然是“如果有Widget不用了,那就把它从HashMap里删掉啊”,但是如何才能知道某个Widget再不被使用了呢?总不能再弄一个引用计数系统吧。
2. 假设我们正在构建一个缓存系统,比方说把硬盘里的图片缓存到内存里,但是内存总是有限的,如果我们在内存中保存的是图片对象的强引用,那么这些对象不会在gc中被释放,这很容易就会导致内存被耗尽,然后发生OOM
弱引用 Weak References
弱引用不足以让对象在内存中强制驻留。也就是说,被弱引用修饰的对象,在gc过程中可能会被回收,你并不需要什么额外的操作。下面是使用范例:
WeakReference<Widget> weakWidget = new WeakReference<Widget>(widget);
我们可以使用weakWidget.get()方法来获取这个弱引用所指向的对象,但是由于这个对象可能已经被gc回收了,所以get到的值可能为null。
对于上面提出的第一种场景,我们可以使用系统内置的WeakHashMap来完成这一工作,WeakHashMap的特点是它的key值是WeakReference(不是value值!),而且如果WeakHashMap的某个key值被自动回收了,它所对应的value也会被自动释放。场景一的问题就这样被轻松解决了。
引用队列 Reference Queues
如果WeakReference的get方法返回null了,那么这个WeakReference所关联的对象已经被释放了,但是这个WeakReference对象本身还是存在的,它会占用空间,为了避免内存泄漏。我们需要一个机制来确保WeakReference也能被释放。
于是有了ReferenceQueue。
如果在创建WeakReference的时候,在构造函数里传入一个ReferenceQueue,那么在这个WeakReference所引用的对象被回收之后,这个WeakReference会被自动插入到ReferenceQueue里来。于是我们可以在适当的时候(比方说后台开一个定时线程)去扫描这个ReferenceQueue,然后把队列里的无用的WeakReference全部清除掉。
软引用 Soft References
软引用与弱引用极其类似,但是软引用相比之下并不急于释放所引用的对象。
弱引用不会影响gc,弱引用指向的对象只要被gc扫描到,就会被释放,但是由于gc线程的优先级很低,而且这个对象可能已经被提升到了老年代,所以这个对象还是可能存活很久。
软引用会影响gc,被软引用指向的对象只有在内存不足的时候,才会被释放掉(generally retained as long as memory is in plentiful supply)。
我个人的理解就是:软引用指向的对象可以无视young gc,只在full gc里被回收。弱引用的对象碰到young gc也会被回收。
幻引用 Phantom References
幻引用的get方法永远返回null(PhantomReference重写了父类Reference的get方法,其get方法里就一行代码:return null),也就是说一旦一个对象被放到幻引用里,就再也不可能通过这个幻引用找到它了。
它唯一的作用就是在对象被真正回收之后,放到ReferenceQueue里(因此它只有一个带有ReferenceQueue的构造方法)。
它与WeakReference的区别是:
WeakReference:一旦探测到对象只有WeakReference,就将其放入ReferenceQueue(WeakReference are enqueued as soon as the object to which they point becomes weakly reachable. This before finalization or garbage collection has actually happened.)
PhantomReference:只有在对象被真正销毁之后,才会被放入到ReferenceQueue中(PhantomReferences are enqueued only when the object is physically removed from memory.)
很奇怪,网上很多中文资料里写的都是错的。
稍微总结一下,按引用强度来划分:
强引用>软引用>弱引用>幻引用
ps: ReferenceQueue还与FinalReference有相当的关联,具体请查看参考资料中的那篇infoq的文章
参考资料
https://community.oracle.com/blogs/enicholas/2006/05/04/understanding-weak-references
https://www.zhihu.com/question/49760047/answer/123486092
http://www.infoq.com/cn/articles/jvm-source-code-analysis-finalreference