为什么在Java中需要弱引用?

19

我知道弱引用对象会受垃圾回收器的影响,我们不能保证弱引用会一直存在。我没有看到需要使用弱引用的必要性,但肯定有原因。

  • 在Java中为什么需要使用弱引用?
  • Java中弱引用的实际用途有哪些(一些)?如果您能分享您在项目中如何使用它将会很棒!

3
这个特性非常罕见。对于访问这个问题的每个人来说,建议仔细阅读以下内容:http://weblogs.java.net/blog/2006/05/04/understanding-weak-references - nhahtdh
部分解释:https://dev59.com/M3VC5IYBdhLWcg3w4Vb6 - Denis Tulskiy
可以进行网络搜索(并不是没有尝试过),但您无法获得以下的深入见解或答案。 - 18bytes
检查 Android 中的这个示例:https://dev59.com/pHVC5IYBdhLWcg3wYQEp#21441743 - Artur A
相关问题:何时使用WeakHashMap或WeakReference? - Basil Bourque
@nhahtdh,此帖现在可在以下网址找到:https://web.archive.org/web/20061130103858/http://weblogs.java.net/blog/enicholas/archive/2006/05/understanding_w.html - mjn42
8个回答

20

使用弱哈希表通常是一个不好的想法。首先很容易出错,更糟糕的是它通常被用于实现某种类型的缓存。

这意味着以下内容:您的程序在一段时间内以良好的性能运行正常,在压力下我们会分配越来越多的内存(更多请求=更多内存压力=可能更多的缓存条目),这会导致垃圾回收。

现在,在系统处于高负荷状态时,您不仅会遇到垃圾回收,还会失去整个缓存,而这正是您最需要它的时候。这个问题并不好解决,因此您至少需要使用合理大小的硬引用LRU缓存来减轻这个问题-然后您仍然可以使用弱引用作为额外的辅助。

我曾见过不止一个项目遭受了这个“bug”的困扰...


+1 分享,我也认为软引用更适合缓存。 - bpgergo
@bpgergo 我总是不得不在文档中查找哪个是哪个。实际上,弱引用是更弱的(我应该能记住这一点),所以问题在那里不太明显,因为Hotspot将在每次GC时删除引用。这基本上意味着你只在GC之间使用缓存,而且由于小的GC经常发生,你一开始就不会使用很多缓存。真正的问题实际上是软引用,因为应用程序运行良好一段时间,然后出现真正的性能问题。稍微修改了一下帖子以澄清这一点。 - Voo
1
弱哈希映射有一个很好的使用案例:用于缓存不可变对象的实例,特别是如果应用程序代码经常相互比较。如果我创建了一个与已存在强引用缓存中的对象实例相同的对象实例,则放弃新对象并使用旧对象可能更好 - 且不太可能更糟。优势在于其他强引用的存在;缓存中存在强引用不存在的对象不会提高性能。 - supercat
1
@Voo 不完全同意,这都是一种权衡。Guava Cache 作为一个例子广泛使用弱引用以及其他缓存解决方案,如 EHCache、Cassandra、Infinispan。标准的 LRU 缓存技术的问题在于性能较慢。 - Ivan Voroshilin

7
我见过的最“明显合理”的弱引用使用是Guava的Striped,它执行锁条纹化。基本上,如果当前没有线程持有对锁变量的引用,那么就没有理由保留该锁了,是吧?那个锁可能曾经被使用过,但现在不再需要了。
如果我必须为你提供使用哪种引用的规则,我会说,在你可能将来使用某些内容但如果你真的需要它可以重新计算时,软引用是首选;当某个值在引用超出内存后永远不会再被使用时,弱引用尤其优先。例如,如果你为一个地图键类型使用默认的引用相等方法equals(Object)实现,并且该对象停止在其他任何地方被引用,那么你可以确定该条目永远不会再次被引用。

在大多数适合使用WeakReference的情况下,弱引用存在的目的不是为了封装其目标的状态,而是为了封装其身份(即存在于其周围的其他引用集合)。如果该其他引用集合为空,则WeakReference将停止封装任何有价值的内容。 - supercat
@Louis Wasserman 第二部分加1,对于第一部分,我建议将Guava Cache的WeakReference作为映射的值添加进去。这是多种配置选项之一。WeakReference对于例如短期可运行任务的FIFO链非常有用。一旦整个链计算完毕,弱引用就可以被GC回收了。 - Ivan Voroshilin

5

我使用弱引用的主要原因是通过WeakHashMap间接实现。

你可能想在Map中存储一组对象(作为缓存或任何其他原因),但不希望它们在Map存在期间一直保留在内存中,特别是如果这些对象相对较大。

通过使用WeakHashMap,您可以确保引用不是唯一使对象保留在内存中的东西,因为如果不存在其他引用,则该对象将被垃圾回收。


3

如果您需要在对象被引用时保留某些信息,但不知道何时该对象会消失,您可以使用弱引用来跟踪这些信息。


2

是的,它有很好的影响。

以上"widget serial number"问题为例,最简单的方法是使用内置的WeakHashMap类。 WeakHashMap的工作方式与HashMap完全相同,只是键(而不是值!)使用弱引用进行引用。如果一个WeakHashMap键变成垃圾,它的条目将自动被删除。这避免了我所描述的陷阱,并且除了从HashMap切换到WeakHashMap之外,不需要进行任何更改。如果您按照通过Map接口引用映射的标准约定,那么没有其他代码需要知道这种变化。

参考


1
你应该真正发布你引用的参考资料。 - accordionfolder

1

解耦发布-订阅或事件总线

WeakReference 在你希望一个对象可以被垃圾回收而不必从持有对其引用的其他对象中优雅地移除自己时非常有用。

发布-订阅 或事件总线等场景中,会保存一组订阅对象的引用。这些引用应该是弱引用,以便订阅者方便地离开作用域而无需取消订阅。在所有应用程序中释放其强引用后,订阅者可以“消失”。此时,订阅列表或事件总线不需要继续保留对订阅者的引用。使用 WeakReference 允许对象继续进入遗忘之路。

订阅对象可能是由某个第三方对象提交到发布-订阅或事件总线中的,而不知道自己已经被订阅。在订阅者的生命周期后期协调调用取消订阅可能会非常麻烦。因此,让订阅者消失而不正式取消订阅可能会大大简化代码,并且如果取消订阅协调失败,则可以避免出现困难的错误。

这是一个线程安全的弱引用集合示例,可以在此答案此答案中看到。
    this.subscribersSet =
            Collections.synchronizedSet(
                    Collections.newSetFromMap(
                            new WeakHashMap <>()
                    )
            );

请注意,集合中的条目实际上直到垃圾回收实际执行后才被删除,如上面链接的答案所讨论的那样。当作为垃圾回收的候选项存在(没有任何剩余的强引用保留在任何地方),该项仍然存在于集合中。

1
弱引用对象是JVM平台所需的,以确保防止内存泄漏。
正如Java开发人员所知,Java可能会泄漏,比预期的还要多。这种情况尤其真实,当一个对象不再使用但某些集合仍然强烈引用该实例时:事实上,这是一种非常简单但经常出现的内存泄漏示例,因为只有在存在强引用时,该内存区域才不会被释放。
在上述情况下,集合中的弱引用对象确保:没有强引用链,但只有弱链,实例可以被选为垃圾可收集的。
我认为,Java平台提供的所有功能在某种程度上都很有用:非常熟练的程序员可以编写非常高质量的代码,使Java变得像C ++一样快速和可靠。

0

抱歉,我不明白你的意思。你所说的“确定性”是什么意思?GC本质上是非确定性的,这意味着:程序员引入的内存管理可预测性在GC中无法存在(它始终是一种启发式算法)。通常情况下,GC的调整不能增强上述的确定性。 - Paolo Maresca
我的评论有点讽刺,因此术语被随意使用。但是,通过使用涉及弱引用的技巧,可以找出所观察的对象是否已经被垃圾回收。因此,实际上可以窥视Java完全不透明的垃圾回收,并在必要时等待对象被垃圾回收后再继续执行。在这个意义上“确定性”得以实现。 - jpe

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