弱引用有实际用途吗?

102

可能重复问题:
弱引用 - 它们有多有用?

由于弱引用可以被垃圾回收器在任何时间收回,那么使用它们还有什么实际的理由吗?


3
了解弱引用、软引用和虚引用的含义 - 解释了弱引用、软引用和虚引用 - Piotr Gwiazda
1
事件(回调函数)是一个好的设计模式,事件应该总是使用弱引用。不幸的是,在C#中,尽管事件是一种语言特性,但它们总是使用强引用。这是语言的一个错误,导致程序中出现了很多难以发现的内存泄漏问题(有一些库可以解决这个问题,比如CAB或开源等效的bbv.Common.EventBroker)。 - BlueRaja - Danny Pflughoeft
2
@BlueRaja-DannyPflughoeft,仅将事件引用设置为弱引用而不进行其他更改会将内存泄漏问题转变为非确定性的过早释放问题,这种情况更糟糕。 - Rotsor
@Rotsor:这个问题有一个简单的解决方案,在虚拟机的一侧 - 在垃圾回收期间,将当前运行事件的任何对象视为具有引用。但是在这里没有必要提出这个小实现细节... - BlueRaja - Danny Pflughoeft
@BlueRaja-DannyPflughoeft 让我们在聊天室中讨论这个问题。 - Rotsor
在进行回调后,将前一个活动的引用设置为null,这样怎么样? - Ahmad Shahwaiz
9个回答

53

如果您想在某个其他地方使用时保留对某个内容的引用,比如一个Listener,您可以使用弱引用。

WeakHashMap可被用作键到派生数据的短期缓存。它也可用于保持有关对象的信息,这些对象在其他地方使用且您不知道何时这些对象将被丢弃。

顺便说一下,软引用类似于弱引用,但它们不会立即被清理。GC会尽可能清除弱引用,并在可能的情况下保留Soft Reference。

还有另一种引用称为幻影引用。它用于GC清理过程并引用无法访问“正常”代码的对象,因为该对象正在被清理。


1
是的 :( 我目前解决缓存问题的方法是只使用一个固定大小的强缓存,并在必要时微调缓存大小。如果程序将在许多不同的配置上运行,这远非最佳选择——在这种情况下,分层方法可能值得额外的复杂性。 - Voo
1
值得一提的是,LinkedHashMap 支持简单的 LRU 缓存,建议查看。 - Peter Lawrey
1
@Voo,你可以将最常用的条目固定在顶部,但如果你能这样做,为什么不自己完成所有的事情呢? - bestsss
@bestsss 我猜你的意思是为什么要使用Peter提出的分层方法?我认为与单个固定大小缓存相比,优势在于我们可以保证性能,但仍然可以利用额外的内存来加速 - 这似乎很有用。不会出现唯一软引用缓存的病态行为,但我们仍然可以利用可用内存。 - Voo
1
一个好的缓存不应该仅依赖于固定大小,而是应该采用一些超出LRU的使用统计。如果你这样做,你就不再需要软引用了。此外,如果你保留直接缓冲区或任何其他本地对象,软引用的行为会很糟糕。它们只适用于内存密集型缓存。 - bestsss
显示剩余4条评论

30
由于弱引用可以随时被垃圾收集器回收,使用它是否有任何实际意义呢?当然存在实际意义。如果框架设计者花费巨大代价构建一个不切实际的弱引用系统,那将非常奇怪,不是吗?我认为你想问的问题是:人们在什么样的实际情况下会使用弱引用?有很多情况。一个常见的情况是为了实现性能目标。在调整应用程序的性能时,通常必须在更多的内存使用和更多的时间使用之间进行权衡。例如,假设有一个复杂的计算,你必须执行多次,但计算是“纯”的-答案仅取决于参数,而不取决于外部状态。你可以构建一个缓存-从参数到结果的映射-但这将使用内存。你可能永远不会再问这个问题,那么这个内存就浪费了。弱引用可能解决这个问题;如果同样的问题被多次提问,则缓存可以变得非常大,因此节省了时间。但是,如果缓存变得足够大以至于垃圾收集器需要回收空间,它可以安全地这样做。缺点当然是,垃圾收集器的清理策略是针对整个系统的目标进行调整的,而不是针对您特定的缓存问题。如果GC策略和您所需的缓存策略足够一致,则弱引用是解决此问题的高度实用解决方案。

8
这种情况的问题在于你希望垃圾回收(GC)以期望的方式运行并执行一些与优化算法本质相关的工作。我认为,在大多数常见的GC策略实现中,对于没有强引用的对象的弱引用很快就会被清除 - 通常是在第一次GC通行时。我甚至认为最好使用强引用来实现对这些“答案”的跟踪,并根据自己的领域知识和逻辑明确地丢弃它们。 - Abdul Hfuda
1
为什么WeakHashMap很糟糕,或者使用弱引用作为缓存的缺点。显然,停止全局GC只会很少运行,但我们正在远离它。您在最后一段中暗示了这个问题,但问题是:我不想使用劣质的GC来使用弱哈希映射 - 至少在Java中,那些新的并发GC非常好(不知道.NET在这方面走得有多远,但我确定他们正在朝着同样的方向前进)。 - Voo
11
不应将弱引用作为快速脏缓存使用,过于积极的垃圾回收可能会立即逐出引用,从而得不到任何好处。将引用用作缓存会使程序依赖于GC算法或仅依赖于GC的参数,因此无法可靠地预测。 - bestsss
1
@Voo,WeakHashMap根本不能用作缓存,您希望值是软的,而不是键。 (Soft)RefMaps作为缓存确实可以很好地工作...除非内存变得紧张,应用程序进入恶性循环:由于缓存失败。在早期(约2003年),我喜欢通过软引用使用快速和脏缓存,但它们并不可靠,特别是没有对GC的控制,缓存不应该是配置GC的驱动力。 - bestsss
1
为什么你建议将没有其他引用的对象缓存是使用WeakReference的实践?一个更好的用途,不假设垃圾回收不频繁,是管理通知。一个对象可能希望在发生某些事件时更新另一个对象,只要有人对看到这些更新感兴趣,但不想为了接收更新而保留那个对象,一旦没有人再关注它们。 - supercat
显示剩余2条评论

16
如果一个WeakReference是对象的唯一引用,且希望该对象继续存在,则应该使用SoftReference

当对象还有其他的引用,但你不能(或不想)检测这些引用是否不再使用时,最好使用WeakReferences。此时,其他引用将防止对象被垃圾回收,而WeakReference只是另一种访问同一对象的方式。

两个常见的用例是:

  1. 用于保存特定对象的附加信息(通常是昂贵计算但可再现的),但不能直接修改该对象,且对其生命周期控制较少。使用WeakHashMap来保存这些引用是完美的方式:WeakHashMap中的键仅由弱引用持有,因此当键被垃圾回收时,值也会从Map中删除,从而可以被垃圾回收。
  2. 用于实现事件或通知系统,其中“监听器”向某种协调器注册,以便在发生某些事情时通知它们,但不希望防止这些监听器在其生命周期结束时被垃圾回收。当原始对象被垃圾回收后,WeakReference将在对象仍然存在时指向该对象,但指向“null”。

13

我们出于这个原因使用它 - 在我们的例子中,我们有许多必须向服务注册的监听器。服务保留对监听器的弱引用,而实例化的类则保留强引用。如果任何时候类被垃圾回收了,弱引用就是监听器的全部,随之也会被垃圾回收。这使得跟踪中间类变得更容易。


8

弱引用最常见的用法是用于"查找" Maps 中的值。

使用普通(硬)值引用时,如果映射表中的值不再具有其他地方的引用,那么您通常不再需要查找。而对于弱引用映射值而言,一旦它没有其他引用,该对象就成为垃圾收集的候选对象。

映射本身拥有该对象的唯一引用并不会阻止其被垃圾收集,因为该引用是一个引用。


3
最常见的用法...这是不正确的。对于软引用可能是正确的,但绝对不适用于弱引用。弱引用被用于在保留真正引用会导致泄漏的情况下使用。 - bestsss

3
为了防止内存泄漏,请参考这篇文章获取详细信息。

3

弱引用是指不保护所引用对象免于被垃圾回收器回收的引用。

  • 只被弱引用引用的对象被认为是不可达(或“弱可达”),因此可以随时被回收。
  • 弱引用用于避免保留由不需要的对象引用的内存。一些垃圾回收语言具有或支持各种级别的弱引用,如Java、C#、Python、Perl、PHP或Lisp。
  • 垃圾回收用于减少内存泄漏和数据损坏的可能性。有两种主要类型的垃圾回收:跟踪和引用计数。引用计数方案记录给定对象的引用数,并在引用计数变为零时收集该对象。引用计数不能收集循环(或循环)引用,因为一次只能收集一个对象。不直接被其他对象引用且不可达的相互引用对象组因此可能会永久驻留;如果应用程序不断生成这样的不可达对象组,这将导致内存泄漏的效果。如果使用弱引用来解决组内引用循环问题,则可以避免引用循环。
  • 弱引用也用于通过仅弱引用它们来指示程序哪些对象不是关键对象,从而最小化内存中不必要的对象数量。

1
Java(标签中给出的语言)被指定为不使用引用计数GC。 - Bill K
我认为Java GC可以使用引用计数作为其中一种策略,当然这取决于JVM。你能否提供一些资源,说明Java不能使用/从不使用引用计数? - Bhushan
2
@Brushan 语言规范要求正确处理循环引用,这意味着虽然我们可以使用引用计数,但仍然需要定期进行标记和清除操作以打破循环引用。加上引用计数垃圾回收机制相对于当前并发算法的性能问题,使其变得不太有趣。 - Voo

2

我通常使用它作为某种类型的缓存。最近访问的项目可以立即使用,在缓存未命中的情况下重新加载该项目(数据库、文件系统等)。


1
软引用难道不是你的场景更好的选择吗?请参考: - Kumar

0

我使用WeakSet来编码图中的链接。如果一个节点被删除,链接会自动消失。


1
如果我的理解是正确的,那么这极大地依赖于虚拟机。我认为当GC触发时,弱引用会被移除,并且不会强制GC触发。如果GC没有触发,链接就不会消失,对吗?这是一个正确性问题吗?在节点删除后,您是否显式调用GC? - ccoakley
@ccoakley:是的,我调用了 gc.collect()。这样就足以保证它能正常工作了吗?边缘结构很复杂,而且明确断开连接会很麻烦。 - Neil G
我相信你的代码在Sun的JVM上是有条件正确的。然而,JVM规范(第3章)指定:“...使用的垃圾收集算法...由实现者自行决定。”这意味着一个(糟糕的)保守收集器可能不会注意到引用丢失,并且不会在你显式调用gc时删除链接。这就是为什么显式gc调用不受欢迎的原因之一;确切的行为并不完全与平台无关。请注意,我的知识在这个主题上相当有限和过时。 - ccoakley
1
@ccoakley:我正在使用Python,但你说得对:故事是一样的。我必须在某个时候修复它。好的一面是,为了使其工作,我非常谨慎地引用了我的参考资料,因此当我最终明确地从字典中删除对象时,我可以确定没有引用保持链接对象活动。 - Neil G
@BasilBourque 抱歉,这是 Python。我不知道 Java。无论如何,就像 ccoakley说的那样,这并不是一个好的设计。 - Neil G
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接