什么时候使用WeakHashMap或WeakReference?

169

我从未见过对弱引用的实现,因此正在尝试弄清楚它们的用例以及如何实现。您何时需要使用WeakHashMapWeakReference,并且它是如何被使用的?


类似:WeakHashMap 的用法? - Basil Bourque
10个回答

97
强引用的一个问题是缓存,特别是对于像图像这样的非常大的结构。假设您有一个必须使用用户提供的图像(例如我正在处理的网站设计工具)的应用程序。自然地,您希望缓存这些图像,因为从磁盘加载它们非常昂贵,并且您希望避免在内存中同时拥有两个副本(可能巨大)的图像的可能性。

因为图像缓存应该防止我们在不绝对需要时重新加载图像,所以您很快就会意识到缓存始终应该包含对任何已经在内存中的图像的引用。然而,使用普通的强引用,该引用本身将强制图像保留在内存中,这要求您某种方式确定何时不再需要内存中的图像并将其从缓存中删除,以便它变得可以进行垃圾回收。您被迫重复垃圾收集器的行为并手动确定对象是否应该在内存中。

理解弱引用, Ethan Nicholas


45
在这种情况下,使用SoftReferences是否更好,即只有在内存开始不足时才收集引用? - JesperE
我有点困惑...假设我有一个SWT图像缓存。 SWT图像需要通过dispose()方法进行处理以释放SO资源。如果我使用WeakHashMap来存储它们,那么GC将如何处理这个对象? - marcolopes
2
@marcolopes GC 会在任何其他对象上使用终结器。似乎 SWT 不喜欢这样做,因此我认为您不能使用 WeakHashMap 管理操作系统资源。 - Jacob Krall
1
@marcolopes,(我会假设您的GC在回收内存之前保证调用finalize。)如果dispose在缓存终结器中完成,那么一切都很好。如果dispose是您必须手动调用的内容,则可以选择1)扩展类并将dispose放入终结器中,或者2)使用虚引用来跟踪和运行相应的dispose。选项2更好(避免复活错误,并使您能够在另一个线程上运行dispose),但选项1更容易实现而无需辅助类。 - Pacerier
3
以下是网址为https://web.archive.org/web/20061130103858/http://weblogs.java.net/blog/enicholas/archive/2006/05/understanding_w.html的文章翻译:理解Web 2.0的本质因为“Web 2.0”已成为一个流行的术语,但却没有一个清晰的定义,所以它很容易被滥用或误解。事实上,“Web 2.0”并不是指新的技术或标准,而是一种关于Web的新的哲学和价值观。Web 1.0主要是一个静态的、由内容提供者控制的Web世界。然而,在Web 2.0中,用户可以更直接地参与其中,创造内容和应用,从而使Web变得更加动态和开放。这种变化受到了许多因素的推动,包括广泛的宽带网络、社会软件(如博客和维基百科)、服务组合和互操作性。当今的Web更像是一个全球大脑,它不断演化和学习。通过开放和参与,我们能够更好地理解和利用这个Web,为我们的生活和工作带来更多的好处。 - mjn42
我认为弱引用和软引用对于通用缓存来说过于不确定性,无法发挥作用。您使用的GC策略将严重影响缓存的行为。因此,这种方法似乎并不是非常有用。 - AminM

61

WeakReferenceSoftReference

需要明确的是,WeakReferenceSoftReference之间的区别。

基本上,一旦引用对象没有引用,WeakReference将被JVM迅速地GC-d。另一方面,SoftReference对象通常会被垃圾回收器保留,直到它真正需要回收内存。

如果在缓存中使用WeakReference来保存,那么这将是相当无用的(在WeakHashMap中,是键被弱引用)。当你想要实现一个可以随着可用内存而增长和缩小的缓存时,可以使用SoftReferences来包装值。


4
“如果缓存中的值保存在WeakReferences内,那么这个缓存将会非常无用。” 我完全不同意。 - Thomas Eding
5
当你不再引用缓存的值时,它们立即消失的缓存为什么会是一个有用的东西呢?嗯,我真的不知道该说什么。 - oxbow_lakes
4
对于类似记忆化的算法,一个宽松地保留其缓存值的高速缓存可能会很有用。 - Thomas Eding
7
我仍然不理解。只有在没有其他引用时,缓存才显得有用... 如果你有对它的引用,那么你为什么需要缓存呢? 如果有其他对同一数据的访问,缓存就会从中获益。当你需要频繁地访问一个数据时,将它存储在缓存中可以提高访问速度并减少系统资源的使用。即使有对该数据的其他引用,缓存也可以作为一个快速访问的副本。 - Cruncher
2
@ThomasEding,Softref告诉环境“存储此内容,直到没有内存可用”。Weakref告诉环境“存储此内容,直到GC运行”。实际上,除非您正在调试/分析GC本身,否则没有使用弱引用的用例。如果您想要一个内存敏感的缓存,请使用softref。如果您不想要缓存,请不要缓存它!那么弱引用在哪里发挥作用呢? - Pacerier
显示剩余5条评论

33
一种常见的使用弱引用和WeakHashMap的方法是为对象添加属性。有时您想要向对象添加一些功能或数据,但在这种情况下,子类化和/或组合不是选项,那么明显的做法就是创建一个哈希映射,将要扩展的对象与要添加的属性链接起来。然后,每当需要该属性时,只需在地图中查找即可。然而,如果您要向其添加属性的对象经常被销毁并重新创建,那么您可能会在地图中留下许多旧对象,从而占用大量内存。
如果您改用WeakHashMap,则对象将在其余程序不再使用它们时立即离开地图,这是期望的行为。
我不得不这样做来添加一些数据到java.awt.Component,以解决JRE在1.4.2和1.5之间的变化。我本可以通过对每个感兴趣的组件(JButton,JFrame,JPanel等)进行子类化来修复它,但这样做更容易,代码也更少。

1
WeakHashMap如何知道“它们不再被程序的其他部分使用”呢? - Vinoth Kumar C M
2
一个弱哈希表使用弱引用作为它的键。当一个对象只通过弱引用引用时,垃圾回收器会“通知”弱引用的所有者(在这种情况下是WeakHashMap)。我建议阅读Java文档中关于WeakReferences和ReferenceQueues的内容,以了解这些对象如何与垃圾回收器交互。 - luke
1
谢谢Luke,你能提供一下你上面描述的简单代码吗? - boiledwater
因此,WeakReference 只有在 Java 中才有意义,这是由于 Java 的古怪 API,其中一些类无法被扩展。 - Pacerier

25
另一个使用 WeakHashMapWeakReference 的有用场景是实现监听器注册表

当你创建想要监听某些事件的东西时,通常会注册一个监听器,例如:

manager.registerListener(myListenerImpl);

如果manager使用WeakReference存储您的侦听器,那么您无需使用manager.removeListener(myListenerImpl)等方法来移除注册,因为一旦您的侦听器或持有侦听器的组件不可用,它就会自动删除。

当然,您仍然可以手动删除侦听器,但是如果您不这样做或忘记这样做,它将不会导致内存泄漏,并且不会阻止垃圾收集器回收您的侦听器。

WeakHashMap在哪里发挥作用?

希望将已注册侦听器存储为WeakReference的侦听器注册表需要一个集合来存储这些引用。标准Java库中没有WeakHashSet实现, 只有WeakHashMap, 但我们可以轻松地使用后者来“实现”前者的功能:

Set<ListenerType> listenerSet =
    Collections.newSetFromMap(new WeakHashMap<ListenerType, Boolean>());

通过使用listenerSet来注册新的监听器,您只需要将其添加到集合中,即使没有明确删除该监听器,如果该监听器不再被引用,则JVM也会自动删除它。


12
使用weakHashSets作为监听器列表的问题在于,在register()中创建的匿名监听器实例很容易丢失,这可能会让用户感到意外。相比之下,从管理器中持有监听器的强引用更加安全,而且可以依赖调用者来做正确的事情。 - Gunanaresh
对于监听器注册实现:所有已注册的监听器将在下一次GC启动时被收集/销毁?例如,在触发所有监听器的onSomethingHappened()方法时,如果GC启动了会发生什么? - blackkara
@icza,我简直不敢相信人们仍在传播这个谬论。这是完全错误的答案。你可能会说另一个WeakHashMap有用的情况是每当你需要一个对象的HashMap时。所以哇,你永远不必手动执行hashmap.remove,因为一旦对象超出范围,项目就会自动删除!真是魔法!这样一个丑陋的魔法黑客是一个完全的脸掌 - Pacerier
4
@Pacerier:我从JavaScript领域的其他评论中跟踪了你的链接,一直到这里,但仍然不太明白为什么使用WeakMap实现监听器注册表是一个谬论。例如,如果WebSocket客户端应该通过注册表服务与某些侦听器绑定,那么将套接字对象存储为WeakMap中的键似乎是合理的(以防止它们在连接关闭后挂起在内存中,例如,在发生错误时),并且能够在需要时检索它们的所有侦听器。那么,请你说明,这种方法有什么问题? - Damaged Organic
2
@Pacerier,我也不明白你的反对意见。在发布-订阅或事件总线场景中,一组弱引用对我来说是很合理的。订阅对象可以超出范围并进入垃圾回收,而不需要正式取消订阅。如果第三方对象负责初始订阅而没有订阅对象的知识,则取消订阅的过程可能会特别复杂。弱引用集合极大地简化了代码库并避免了与未能取消订阅有关的不必要错误。有什么缺点呢? - Basil Bourque
@Pacerier 在调用匿名或缺失的观察者之前,是否考虑创建一个强引用以确保其仍然存在?这样可以修复在时钟滴答或类似情况下立即调用它们的错误。 - Nicolas NZ

5
这篇博客展示了两个类的使用:Java:对ID进行同步。使用方法如下:
private static IdMutexProvider MUTEX_PROVIDER = new IdMutexProvider();

public void performTask(String resourceId) {
    IdMutexProvider.Mutex mutext = MUTEX_PROVIDER.getMutex(resourceId);
    synchronized (mutext) {
        // look up the resource and do something with it
    }
}

IdMutextProvider提供基于ID的对象来进行同步。其要求如下:

  • 对于等价的ID,必须返回到相同对象的引用以进行并发使用
  • 对于不同的ID,必须返回不同的对象
  • 没有释放机制(对象不会被返回给提供者)
  • 不能泄漏(未使用的对象可进行垃圾回收)

这是通过使用内部存储映射类型来实现的:

WeakHashMap<Mutex, WeakReference<Mutex>>

这个对象既是键也是值。当地图外没有任何对该对象的硬引用时,它可以被垃圾回收。地图中的值是使用硬引用存储的,因此必须将值包装在WeakReference中以防止内存泄漏。这最后一点在javadoc中有详细说明。


4
如果您想跟踪某个类创建的所有对象,为了仍然允许这些对象被垃圾回收,您可以将对象本身替换为对这些对象的弱引用列表/映射。

现在如果有人能向我解释虚引用,我会很高兴...


3
一个用途:PhantomReferences可以让你确定一个对象何时从内存中被移除。它们实际上是唯一的确定方式。 - Jacob Krall
实际上,除非您明确清除它,否则它不会被删除。 “与软引用和弱引用不同,幽灵引用不会在入队时由垃圾收集器自动清除。通过幽灵引用可访问的对象将保持不变,直到所有这些引用都被清除或它们本身变得不可访问。” - jontro
@jontro,但它已经被确定了(http://archive.is/jOT7Q),所有成员都离开了。实际上,它是一个空白的对象。请参见https://dev59.com/NWw05IYBdhLWcg3wwEcS - Pacerier

3
如上所述,只要强引用存在,弱引用就会保持。
一个例子是在监听器中使用WeakReference,这样一旦目标对象的主引用消失,监听器就不再活动。请注意,这并不意味着从监听器列表中删除WeakReference,仍然需要进行清理,但可以在预定时间执行。这也有助于防止被监听的对象持有强引用并最终成为内存膨胀的来源。例如:Swing GUI组件引用比窗口寿命更长的模型。
当像上面描述的那样使用监听器时,我们很快意识到从用户角度来看,对象会被“立即”收集。

谢谢有用的回答。但我在想,在这种情况下,监听器是否应该被强引用注册? - blackkara
这个答案完全是错误的。详见:https://dev59.com/M3VC5IYBdhLWcg3w4Vb6#_a2gEYcBWogLw_1b0OGo - Pacerier
1
@Pacerier - 对于“弱引用(WeakReferences)”,你的评论完全是错误的! - user177800

2

我在实际应用中使用弱引用的一个例子是,当你有一个很大的对象,但它很少被使用时。你不想在不需要它时将其保留在内存中;但是,如果另一个线程需要相同的对象,你也不希望在内存中有两个对象。你可以在某个地方保持对该对象的弱引用,并在使用它的方法中保留对该对象的硬引用;当这些方法都完成时,该对象将被收集。


1
这是一个软引用,而不是弱引用。请参见https://dev59.com/M3VC5IYBdhLWcg3w4Vb6#155492。 - Pacerier

0

-1

你可以使用WeakHashMap来实现资源免费的缓存,用于大量对象的创建。

但请注意,不要使用可变对象。 我将其用于缓存查询结果(大约需要400毫秒执行)到文本搜索引擎中,这个搜索引擎很少更新。


你所说的是软引用,而不是弱引用。请参考 https://dev59.com/M3VC5IYBdhLWcg3w4Vb6#155492。 - Pacerier

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