不久前,我面试了一些高级Java开发人员职位的候选人。我经常采访他们说,“你能给我介绍一下Java中的一些弱引用吗?”如果面试官说,“嗯,跟垃圾回收有关吗?”,我基本就满意了。不要期望答案是好奇的论文描述。然而,事与愿违。我惊奇地发现,在近20名平均有5年开发经验、高学历的应聘者中,只有两人知道弱引用的存在,但真正了解这一点的也只有一人。面试过程中,我也试着提醒了一些事情,看看有没有人突然说“原来如此”,结果让我很失望。我开始怀疑为什么这条知识被如此忽视。毕竟弱引用是一个非常有用的特性,而且这个特性是在7年前Java1.2发布时引入的。好吧,在这里我不指望你看完这篇文章就成为弱引用方面的专家,但我想你至少应该了解什么是弱引用,如何使用弱引用,以及在什么场景下使用。由于是一些不为人知的概念,我就对前三个问题做简单的解释。强引用(StrongReference)强引用是我们经常使用的引用,它的写法如下1StringBufferbuffer=newStringBuffer();上面创建了一个StringBuffer对象,这个对象的(强)引用存储在变量buffer中。没错,就是这个儿科手术(原谅我这么说)。强引用最重要的是它可以使引用变强(Strong),这决定了它与垃圾收集器的交互。具体来说,如果一个对象通过一连串的强引用链接是可达的(Stronglyreachable),那么它就不会被回收。如果您不想回收正在使用的对象,这正是您所需要的。但是强引用就是这么强。在程序中,将类设置为不可扩展的情况并不常见。当然,这可以通过将类标记为final来实现。或者可以更复杂一些,即通过一个工厂方法返回一个接口(Interface),里面包含了未知数量的具体实现。比如我们要使用一个类叫Widget,但是这个类不能被继承,所以不能增加新的功能。但是,如果我们想跟踪有关Widget对象的附加信息,我们应该怎么做呢?假设我们需要记录每个对象的序号,但是由于Widget类没有这个属性,而且不能扩展,所以我们不能增加这个属性。其实一点问题都没有,HashMap完全可以解决上面的问题。1serialNumberMap.put(widget,widgetSerialNumber);这表面上看起来没什么问题,但是widget对象的强引用很可能会出问题。我们可以确定,当不需要widget序列号时,我们应该从地图中删除该条目。如果我们不移除它,可能会导致内存泄漏,或者我们在手动移除它时删除了我们正在使用的widget,这将导致有效数据丢失。事实上,这些问题非常相似。这是没有垃圾回收机制的语言在管理内存时经常遇到的问题。但是我们不用担心这个问题,因为我们使用的是带有垃圾回收机制的Java语言。强引用的另一个可能问题是缓存,特别是对于像图像这样的大文件。假设你有一个程序需要处理用户提供的图像。通常的做法是缓存图片数据,因为从磁盘加载图片的开销很大,同时我们要避免在内存中同时拥有相同图片数据的两份副本。缓存的目的是防止我们再次加载不需要的文件。您很快就会发现缓存将始终包含对内存中已有图像数据的引用。使用强引用会强制图像数据留在内存中,这就需要你决定何时不需要图像数据,手动将其从缓存中移除,以便垃圾收集器回收。所以你又被迫做垃圾收集器应该做的事情,人为地决定哪些对象应该被清理。弱引用(WeakReference)弱引用简单来说就是将对象保存在内存中的能力不是那么强的引用。使用Wea??kReference,垃圾收集器将帮助您决定何时回收引用的对象并将该对象从内存中移除。创建弱引用如下1WeakReferenceweakWidget=newWeakReference(widget);使用weakWidget.get()获取真正的Widget对象,因为弱引用无法阻止垃圾回收器对其进行回收,你会发现(当没有对widget对象的强引用时)使用get时突然返回null。解决上述widget序列号记录问题的最简单方法是使用Java内置的WeakHashMap类。WeakHashMap与HashMap几乎相同,唯一的区别是它的键(而不是值!!!)由WeakReference引用。当WeakHashMap的key被标记为垃圾时,这个key对应的条目会被自动移除。这样就避免了上面手动删除不需要的Widget对象的问题。使用Wea??kHashMap可以很容易地转换为HashMap或Map。引用队列(ReferenceQueue)一旦一个弱引用对象开始返回null,这个弱引用指向的对象就被标记为垃圾。而这个弱引用对象(不是它指向的对象)是没有用的。通常此时需要进行一些清理。比如WeakHashMap此时会去掉无用的entry,避免保存无意义的弱引用抑制增长。引用队列可以很容易地跟踪不需要的引用。当你在构造WeakReference时传入一个ReferenceQueue对象,当该引用指向的对象被标记为垃圾时,该引用对象会自动加入到引用队列中。接下来可以固定周期处理传入的引用队列,比如做一些清理工作来处理这些无用的引用对象。四种引用Java中其实有四种不同强弱的引用。从强到弱分别是强引用、软引用、弱引用和虚引用。上面部分介绍了强引用和弱引用,下面介绍剩下的两种,软引用和幻引用。软引用(SoftReference)软引用与弱引用基本相同,只是它比弱引用具有更强的防止垃圾回收回收其指向的对象的能力。如果一个对象可以被弱引用访问,那么该对象将在下一个收集周期被垃圾收集器销毁。但是如果能达到软引用,那么这个对象就会在内存中停留更长时间。当内存不足时,垃圾回收器会回收这些软引用可达的对象。由于软引用可达的对象在内存中的停留时间比弱引用可达的对象长,我们可以利用这个特性进行缓存。这样可以省去很多工作,垃圾回收器会关心当前可达的是哪种类型,处理它消耗了多少内存。幻引用(PhantomReference)不同于软引用和弱引用。虚引用指向的对象非常脆弱。我们无法通过get方法得到它指向的对象。它唯一的作用是当它指向的对象被回收时,加入到引用队列中,记录引用指向的对象已经被销毁。当弱引用指向的对象变为弱引用可达时,弱引用将被添加到引用队列中。此操作发生在对象销毁或垃圾??收集实际发生之前。理论上,可以在非兼容的析构方法中复活即将被回收的对象。但是这个弱引用会被销毁。虚引用只有在它指向的对象从内存中移除后才会被添加到引用队列中。它的get方法总是返回null以防止它指向的几乎被破坏的对象被复活。幻象引用主要有两种使用场景。它使您可以准确地知道它引用的对象何时从内存中删除。实际上这是Java中唯一的方法。在处理像图像这样的大文件时尤其如此。当你确定一个图像数据对象应该被回收时,你可以使用幻像引用来确定该对象在回收后将继续加载下一张图像。这最大限度地减少了可怕的内存不足错误。第二点,幻象引用可以避免解构时的很多问题。finalize方法可以通过创建对即将被销毁的对象的强引用来复活这些对象。但是,如果一个重写了finalize方法的对象想要被回收,它需要经历两个独立的垃圾回收周期。在第一个循环中,一个对象在被销毁之前被标记为可回收。但是因为这个物体在销毁过程中还有一点复活的可能。在这种情况下,垃圾收集器需要在对象真正被销毁之前再次运行。因为销毁可能不及时,所以在调用对象的销毁之前需要不确定数量的垃圾收集周期。这意味着在真正清理对象时可能会有很大的延迟。这就是为什么当大部分堆被标记为垃圾时仍然会出现恼人的内存不足错误。使用虚引用,上述情况就会得到解决。当一个虚拟引用被添加到引用队列时,你绝对没有办法得到一个被销毁的对象。因为此时,对象已经从内存中销毁了。因为幻象引用不能用于重新生成它指向的对象,所以它的对象将在第一个垃圾回收周期中被清理掉。显然,finalize方法不建议重写。因为幻象引用显然是安全和高效的,去掉finalize方法可以让虚拟机明显更简单。当然你也可以重写这个方法实现更多。这完全取决于个人选择。总结看到这里,很多人都开始吐槽了,为什么要讲一个十年前的老API,嗯,以我的经验,很多Java程序员对这方面的知识不是很了解,我觉得有些深入了解是必要的,希望您能从本文中有所收获。