SoftReference, WeakReference, PhantomReference的区别

一段时间之前,我面试了一些Java高级工程师职位的候选人。在我问的众多问题中的一个是“谈谈你对Java弱引用的理解”。我并无期待一个相似于技术论文同样的答案。候选人若是回答说“嗯…,这不是和垃圾回收有关系吗?”,我可能就已经很满意了。然而出乎我意料的是我问的二十几个候选人差很少都有五年以上的Java经验,可是只有两我的知道弱引用的存在,只有一我的具备实际的使用经验。我甚至尝试解释一下,以获得“啊哦,是这样啊”的回应,可是并无。我不肯定为何不少人对弱引用不够了解,由于弱引用是从Java 1.2就引入并被普遍使用的功能。html

如今,我不会建议你为了作一个不错的Java开发人员而成为一个弱引用的专家。可是我我的认为你最起码应该了解他们是什么。不然你怎么知道何时应该使用它们?这篇文章会对如下方面作一个扼要的说明:什么是弱引用,怎么使用它们,何时使用它们。java

强引用web

首先让咱们回忆一下强引用。强引用是一个普通的引用,你天天都会使用。例如:面试

StringBuffer buffer = new StringBuffer();编程

建立一个新的StringBuffer()而且保存一个对它的强引用到buffer变量。是的,是的,这是小儿科,可是忍受我一下先。强引用重要的地方(使得它们成为“强引用”的地方)就是它们是怎样和垃圾回收器交互的。确切的说,若是一个对象能够经过一个强引用链条被访问到,它是不能被垃圾回收的。由于你不会想让垃圾回收器销毁你正在工做着的对象。这正是你想要的。缓存

当强引用太强了安全

咱们常常会遇到想扩展一些类的功能可是这些类又不太容易被扩展的情形。有时是类被标记为了final,有时是类太复杂了,好比说一个工厂方法返回一个interface类型的对象,可是对象的具体实现是不知道的(甚至都不可能知道)。假设你必需要使用Widget类,可是因为某种缘由你不可以继承Widget去扩展更多的功能。编程语言

若是咱们须要跟踪保存一些Widget额外的信息的话,应该怎么办?假设咱们须要跟踪保存每个Widget的序列号(Serial Number),可是Widget并无序列号属性。因为Widget不能被继承,咱们也不能添加序列号属性。不要紧,咱们可使用HashMap:工具

serialNumberMap.put(widget, widgetSerialNumber);

乍一看,好像没什么问题。可是对widget的强引用确定会形成问题。咱们不得不清楚的知道何时某个Widget的序列号将再也不被须要,咱们能够把它从HashMap中移除。不然将会产生内存泄漏(若是咱们应该从HashMap移除掉widget的时候却没有移除)或者使人费解的发现咱们丢掉了一些序列号(若是咱们移除掉了还在使用的widget)。若是这些问题很眼熟,它们实际上是非垃圾回收编程语言使用者在管理内存时面临的问题。对于更加文明的编程语言好比Java,咱们是不该该关心这个问题的。网站

强引用另一个常见的问题是关于缓存的,特别是涉及到大的结构,好比图片。假设你的应用程序须要处理用户上传的图片,就像我作过的网站设计工具同样。很天然地,你想要缓存这些图片,由于从磁盘加载它们有很是大的开销,而且你也想避免对一个可能很大的图片同时加载两份到内存。

由于一个图片的缓存可使咱们避免从磁盘从新加载到内存(当咱们并非绝对的须要这样),你可能很快会意识到缓存应当对内存中存在的每个图片都有一个引用。然而使用常规的强引用,将会使得图片一直保存在内存中。这将会使你(和上面同样)不得不以某种方式决定图片在何时再也不须要了,进而能够从缓存中移除,以后GC能够回收掉它。再一次的,你不得不模仿GC的行为而且以人工的方式决定一个对象是否应该留在内存中。

弱引用(WeakReference

弱引用简单的说是一个不足以使得一个对象保留在内存中的引用。弱引用容许你利用GC来断定对象是否可达,而没必要本身来断定。建立弱引用的方式以下:

WeakReference weakWidget = new WeakReference(widget);

而后你在其余地方可使用weakWidget.get() 来获取实际的widget对象。固然弱引用不足以保证对象不被GC回收,因此你可能会突然发现(若是没有强引用指向widget) weakWidget.get() 忽然开始返回null。

解决上面的“widget序列号”问题,最简单的方式是使用内置的WeakHashMap类。除了键(注意:不是值!)使用WeakReference指向之外,WeakHashMap和HashMap工做方式彻底同样。若是WeakHashMap的一个键变成了垃圾,对应的键值对将会自动从中移除。这避免了前面提到的问题,代码也只是从使用HashMap简单的改变为使用WeakHashMap。若是你遵循惯用的用Map接口指向你的map类的风格,其余的代码甚至都不须要知道这种改变。

ReferenceQueue

当一个WeakReference开始返回null时,它所指向的对象变成了垃圾,这个WeakReference对象自己几乎也没什么用了。这意味着有一些清除工做须要作。例如,WeakHashMap不得不移除掉这样的死掉的键值对以免包含愈来愈多死掉的WeakReference。

ReferenceQueue使得跟踪死掉的引用变得简单。若是你给WeakReference的构造方法传入一个ReferenceQueue,那么当这个WeakReference的对象所指向的对象成为垃圾的时候,这个WeakReference对象将会被插入这个ReferenceQueue。接下来,你能够隔一段时间就处理一下ReferenceQueue,作一些对死掉的WeakReference任何你想作的清理工做。

不一样程度的弱引用

到如今我只是提到了弱引用,可是实际上有四种从强到弱不一样强度的引用:强引用,软引用(SoftReference),弱引用(WeakReference)和虚幻引用(PhantomReference)。咱们已经讨论了强引用和弱引用,接下来讨论其余两种。

软引用(SoftReference

SoftReference除了比WeakReference不急于扔掉所指向的对象之外它们是同样的。一个对象若是最强的引用只是WeakReference,将会在下次GC发生的时候被遗弃掉。可是一个对象若是只被软引用指向,简单的讲它将会再坚持存在一段时间。

SoftReference并无被要求和WeakReference有什么不一样的行为,可是被SoftReference引用的对象简单的讲只要内存足够将会一直被保留。这是它们适合做为缓存的根本所在,就像上面描述的图像缓存同样,由于你能够交给GC去关心对象以哪一种方式可达(强引用对象将永远不会从内存移除),GC有多么的须要去回收缓存所占用的内存。

虚幻引用(PhantomReference

PhantomReference与SoftReference和WeakReference都不同。它对于对象的引用很是的虚幻,以至于你甚至没有办法获取被引用的对象—它的get()方法老是返回null。它惟一的用处就是跟踪被引用的对象什么时候进入到ReferenceQueue,从而知道被引用对象什么时候死掉了。然而这和WeakReference有什么不一样呢?

确切的说,不一样的地方在于什么时候进入到ReferenceQueue。WeakReference在它所指的对象被GC认定为只是弱引用可达的时候就会被放到ReferenceQueue中。这个发生在finalize()调用以前或者垃圾回收以前;理论上说所指向的对象甚至能够经过finalize()方法“复活”,可是WeakReference将会保持死亡状态。PhantomReference只有当对象从物理内存中移除的时候才会进入到ReferenceQueue,而且因为它的get () 方法老是返回null,这保证了你不可以“复活”一个濒死的对象。

PhantomReference有什么好处呢?我只知道两个正式使用它的情形:首先它容许你准确的判断一个对象何时从物理内存中移除。实际上这是惟一的判断方式。大体上说这不是很是的有用,可是在某些特定的场合可能会很方便:若是你确切的知道一个图片应当会被回收掉,你能够等到它确实已经从内存中移除了而后再加载下一个图片,由此你能够下降吓人的OutOfMemoryError发生的几率。

其次,PhantomReference避免了对象终结(finalization)固有的问题:finalize()方法能够经过建立强引用来“复活”对象。这又能怎么样?问题是一个复写了finalize()方法的对象,如今必须须要至少通过两轮的GC过程,才能被回收掉。第一轮断定一个对象为垃圾,从而能够去调用它的finalize()方法。因为在finalize()方法中“复活”的可能性(很小但确实存在),GC不得不在真正回收对象以前再跑一轮。而且由于finalize()方法的调用并非即时性的,当对象在等待它的finalize()方法被调用的时候,GC可能已经跑过好多轮了。这意味着从断定对象为垃圾到真正回收内存之间存在着严重的延时,这也是你为何会在大部分对象为垃圾的状况下还会遇到OutOfMemoryError的缘由。

使用PhantomReference,这种状况是不可能的—当一个PhantomReference入队列的时候,你绝对没有办法获取已经死掉的对象(这很好,由于它已经不在内存中了)。由于PhantomReference不能被用来复活一个对象, GC若是发现对象只是被虚幻引用,在第一轮的时候就能够当即回收这个对象。而你能够在任何方便的时候清理资源。

或许finalize()方法原本从最初就不该该被提供出来。PhantomReference使用起来绝对是更安全和更高效的,而且移除掉finalize()方法,会使得JVM的某些部分变得至关的简单。可是PhantomReference的使用会包含比较多的工做,因此我也认可我大部分时间也仍是在使用finalize()。好消息是至少你有一个选择。

总结

我肯定大家中的某些人在喃喃自语,由于我在讨论一个存在了好多年的功能,而并非在讨论一个从未被讨论过的话题。以个人经验,不少Java开发者确实对弱引用不是太了解(若是知道一点的话)。我感受须要写篇文章刷新一下记忆,但愿你能从这篇回顾中有一点儿收获。

 

做者公众号(码年)扫码关注:

 

英文原文:

https://web.archive.org/web/20061130103858/http://weblogs.java.net/blog/enicholas/archive/2006/05/understanding_w.html

相关文章
相关标签/搜索