什么时候使用WeakHashMap或WeakReference?


Answers:


96

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

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

理解弱引用,伊桑·尼古拉斯


43
在这种情况下,SoftReferences不会更好,例如,仅在内存开始用尽时才收集引用。
JesperE

我有点困惑...比方说,我有一个SWT图像缓存。需要通过dispose()方法处理SWT映像以释放SO资源。如果我使用WeakHashMap来存储它们,请确切显示GC会处置该对象吗?
marcolopes 2014年

2
@marcolopes GC将在任何其他对象上使用终结器。当您执行此操作时,SWT似乎不喜欢它,因此我认为您无法使用来管理操作系统资源WeakHashMap
Jacob Krall 2014年

@marcolopes,(我假设您的GC在回收内存之前会保证调用finalize。)如果在缓存终结器中完成处置,那么一切都很好。如果必须手动调用dispose,则可以1)扩展类并将dispose放入终结器中,或2)使用phantomreference跟踪并相应地运行dispose。选项2更好(避免了复活错误,并且使您能够在另一个线程上运行处置),但是选项1更容易实现而无需帮助程序类。
Pacerier


55

WeakReferenceSoftReference

一个明显的区别是a WeakReference和a 之间的区别SoftReference

基本上是WeakReferenceGC-d的JVM热切,一旦引用的对象没有到它的引用。SoftReference另一方面,垃圾收集器往往会留下一个d对象,直到它确实需要回收内存为止。

保存在WeakReferences 内的缓存将毫无用处(在a中WeakHashMap,是弱引用的键)。SoftReferences当您要实现可随可用内存增长和收缩的缓存时,用于包装这些值很有用。


4
我完全不同意“将值保存在WeakReferences内部的缓存将毫无用处”。
Thomas Eding

5
@trinithis-嗯,我真的不知道该说些什么。为什么当您不引用它们时,其值消失的缓存确实有用
oxbow_lakes

4
对于诸如备忘录之类的东西,松散保存其缓存值的缓存可能会很有用。
Thomas Eding

5
@ThomasEding我还是不明白。缓存似乎唯一有用的时间是当没有其他引用时...如果有引用,那么需要什么缓存?
Cruncher 2014年

2
@ ThomasEding,Softref告诉环境“将其存储直到没有内存”。Weakref告诉环境“将其存储到GC运行为止”。坦白说,除非您正在调试/分析GC本身,否则没有弱引用的用例。如果需要内存敏感的缓存,请使用softref。如果您不想缓存,请不要缓存!弱引用从哪里进入?
佩里耶

30

特别是WeakReferences和WeakHashMaps的一种常见用法是为对象添加属性。有时,您想向对象添加一些功能或数据,但是子类化和/或合成不是一种选择,在这种情况下,显而易见的事情是创建一个哈希图,将想要扩展的对象链接到要添加的属性。然后只要您需要该属性,就可以在地图中查找它。但是,如果要添加属性的对象往往会被破坏和创建很多,则最终可能会导致地图中的许多旧对象占用大量内存。

如果使用a WeakHashMap代替,则对象将在程序的其余部分不再使用它们后立即离开地图,这是所需的行为。

我不得不这样做是为了增加一些数据,以java.awt.Component避开在1.4.2和1.5之间的JRE的变化,我可以通过继承每个组件我很感兴趣,INT(固定它JButtonJFrameJPanel...),但是,这是多少更少的代码,更容易。


1
WeakHashMap如何知道“程序的其余部分不再使用它们”?
Vinoth Kumar CM 2011年

2
弱hasmap使用弱引用作为其键。当仅通过弱引用引用对象时,垃圾收集器将“通知”弱引用的所有者(在本例中为WeaHashMap)。我将在javadocs中阅读WeakReferences和ReferenceQueues,以了解这些对象如何与垃圾收集器交互。
路加福音

1
谢谢卢克,您能为您提供一些简单的代码来避免描述吗?
开水时间:2012年

因此,由于Java的古怪API(其中某些类无法扩展),弱引用仅在Java中才有意义
Pacerier

22

对于另一个有用的情况WeakHashMapWeakReference是一个听众注册表实现

当您创建想要监听某些事件的内容时,通常会注册一个监听器,例如

manager.registerListener(myListenerImpl);

如果用来manager存储您的监听器WeakReference,则意味着您不需要删除寄存器,例如使用,manager.removeListener(myListenerImpl)因为一旦您的监听器或包含该监听器的组件不可用,该寄存器就会被自动删除。

当然,您仍然可以手动删除侦听器,但是如果您不删除它或忘记了它,则不会导致内存泄漏,也不会阻止您的侦听器被垃圾回收。

WeakHashMap图片出现在哪里?

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

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

以此listenerSet注册一个新的侦听器,您只需要将其添加到集合中,即使没有显式删除它,如果不再引用该侦听器,JVM也会自动将其删除。


10
将弱势哈希集用于侦听器列表的问题在于,在register()中创建的匿名侦听器实例将很容易丢失,这对于用户而言是意外的。更安全的是,从管理者那里获得对监听者的更强引用,而依靠调用者来做正确的事情。
Gunanaresh 2015年

对于侦听器注册表实现:所有注册的侦听器都将在下一次GC启动时被收集/销毁吗?例如,在触发所有侦听器的onSomethingHappened()方法时,如果踢出GC会怎样?
blackkara

@icza,我不敢相信人们仍在兜售这个神话。这是完全错误的答案。您可能还说另一个有用的情况WeakHashMap是,每当需要一个HashMap对象时。哇,您不必手动执行hashmap.remove 因为一旦obj超出范围,项目就会自动删除!从字面上看是魔术!如此丑陋的魔术骇客简直就是一个完整的面孔
Pacerier

3
@Pacerier:我一直在跟踪您从JavaScript中其他注释的链接,一直到这里,但仍然不太明白为什么用WeakMap实现侦听器注册表是一个神话。例如,如果WebSocket客户端应通过注册表服务与某些侦听器绑定,则将套接字对象作为键存储在WeakMap中似乎是合乎逻辑的(以防止它们在连接关闭(例如,出现错误时)后挂在内存中)并且能够检索所有需要的听众。因此,请您说明一下,这种方法到底有什么问题?
损坏的有机

1
@Pacerier我也无法理解您的反对意见。在“发布-订阅”或“事件总线”方案中,一组弱引用对我而言非常有意义。允许订阅对象超出范围并进入垃圾回收,而无需正式取消订阅。如果第三方对象在不了解订阅对象的情况下负责初始订阅,则取消订阅的过程可能会特别复杂。的集合WeakReference大大简化了代码库,并避免了与无法取消订阅相关的不必要的错误。有什么缺点?
罗勒·布尔克

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中进行了介绍


3

例如,如果要跟踪某个类创建的所有对象。为了仍然允许对这些对象进行垃圾收集,请保留对对象的弱引用列表(而不是对象本身)。

现在如果有人可以向我解释幻影引用,我会很高兴...


2
一种用途:PhantomReferences允许您确切确定何时从内存中删除对象。实际上,它们是确定这一点的唯一方法。(weblogs.java.net/blog/enicholas/archive/2006/05/...
雅各布Krall的

实际上,除非您明确将其清除,否则不会将其删除。“与软引用和弱引用不同,幻象引用在排队时不会被垃圾收集器自动清除。通过幻象引用可访问的对象将保留,直到所有此类引用被清除或它们自身无法访问为止。”
jontro '04 -4-27

@jontro,但是它已经完成了,所有成员都消失了。实际上,它是一个空白obj。参见 stackoverflow.com/q/7048767/632951
Pacerier's

3

如上所述,只要存在强参考,就保持弱参考。

一个示例用法是在侦听器内部使用WeakReference,以便一旦对目标对象的主要引用消失,侦听器将不再处于活动状态。请注意,这并不意味着WeakReference已从侦听器列表中删除,仍然需要进行清理,但可以在计划的时间执行清理。这还具有防止被侦听的对象持有强引用并最终成为内存膨胀的原因。示例:Swing GUI组件所引用的模型的生命周期比窗口长。

如上所述,在与听众一起玩耍时,我们迅速意识到,从用户的角度来看,对象是“立即”收集的。


感谢有用的答案。但是我想知道在那种情况下,是否应该强烈地注册(引用)侦听器?
blackkara

这个答案是完全错误的。详细说明:stackoverflow.com/questions/154724/…–
Pacerier

@Pacerier- WeakReferences您的评论是完全错误的!

2

我对WeakReferences的一种现实用途是,如果您有一个很少使用的非常大的单个对象。您不想在不需要时将其保留在内存中;但是,如果另一个线程需要相同的对象,则您也不希望其中两个在内存中。您可以在某个位置保留对对象的弱引用,并在使用该对象的方法中保留硬引用。当两个方法都完成时,将收集对象。


1
这是一个软引用,而不是一个弱引用。参见stackoverflow.com/a/155492/632951
Pacerier's


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.