【问题标题】:WeakHashMap iteration and garbage collectionWeakHashMap 迭代和垃圾回收
【发布时间】:2011-02-21 03:06:39
【问题描述】:

我正在使用WeaekHashMap 来实现缓存。我想知道我是否正在迭代这个映射的键,同时垃圾收集器正在积极地从这个映射中删除键,我会收到 ConcurrentModificationException 吗? 我不这么认为,因为据我所知,并发修改异常是由于应用程序代码中的错误而发生的,开发人员忘记了同一个映射是由其他线程共享/使用的,在这种情况下,它不应该发生。但是想知道当 WeakHashMap 不同步时 JVM 会如何处理呢?

【问题讨论】:

  • 弱引用无论如何都不适合缓存——它们可以立即被清除,而不是等待垃圾收集器(也不是等待垃圾收集器是一个很好的策略)。

标签: java


【解决方案1】:

正如 bkail 所说,当 GC 从 WeakHashMap 中“删除”一个条目时,它不会导致并发修改。实际上,GC 通过对 WeakReference 对象(持有真正的密钥)本身的硬引用来收集底层对象。因此,不会收集映射直接引用的真实对象(参考对象),因此映射不会更改,直到您的一个线程调用此映射中的方法。那时,map 检查来自 GC 的引用队列并找到所有已收集的键并将它们从 map 中删除 - 因此对 map 结构的实际更改发生在您的一个线程上。

考虑到这一点,可能存在这样一种情况,即您可能会在这样的映射中获得并发修改,而您不会在另一种映射中获得 - 如果您放置一个已经存在的键或调用 getter 方法。但实际上,在并发应用程序中,无论如何您都应该锁定这些调用,这样您的程序中就会出现真正的并发访问错误。

在回答你的问题时说,你真的不应该使用 WeakHashMap 缓存(即使你在谈论缓存键)。在缓存中,当不再引用值时,您不希望您的值“神奇地”消失。通常,您希望它们在达到某个最大数量时消失(例如 Apache 集合 LRUMap)或根据内存需求释放。

对于后者,您可以使用带有SoftReference 的映射(Apache 集合提供了ReferenceMap,允许您指定键或值的引用类型)。软引用被指定为仅根据内存压力释放 - 另一方面,弱引用必须与 GC 一起做更多的事情,因为它认识到对象没有剩余的硬引用并且可以随时释放它。当然,软引用如何真正起作用也取决于 JVM 的实现。

编辑:我重读了您的问题并想解决另一点。因为实际修改发生在您自己的线程上的WeakHashMap 内部结构,如果您只在单个线程中使用此映射,则不需要同步任何方法调用。此行为与任何其他 Map 没有什么不同。

【讨论】:

  • 谢谢凯文。很好的解释,现在我很清楚了。
  • 是的,比我的回答更完整。 +1。
  • 1) 如果一个条目在迭代器到达它之前就消失了会发生什么?难道next()根本就没有返回吗? 2) 如果我从entrySet() 检索到一个条目,并且之后立即对密钥进行 GC。当我拨打entry.getKey() 时会发生什么?
  • 1-是;在内部,迭代器会跳过任何 GC'd 元素,然后在确定下一个条目存在后保留下一个键的临时强引用,因此在您检索它或前进到下一个元素之前,它不能被 GC'd。 2-从条目Set 返回的Entry 是一个常规对象,并持有对键/值的强引用,因此不会被GC。
【解决方案2】:

不,您不会收到 ConcurrentModificationException。 WeakHashMap 在调用各种操作时使用 ReferenceQueue.poll。换句话说,每个调用者都默默地负责从 Map 中清除过时的条目。但是,这确实意味着从多个线程调用 Wea​​kHashMap 上的方法是不安全的,否则这些线程似乎是“只读的”,因为对 get() 的任何调用都可能破坏另一个线程正在尝试迭代的条目链表.

【讨论】:

    【解决方案3】:

    WeakHashMap 在键而不是值上很弱,因此如果您想在不使用值时释放空间,它不适合缓存值。您可能想从google collections 中查看MapMaker

    【讨论】:

    • 这是正确的好建议,但它没有回答问题。
    • @luke 我认为这两种答案都是有效的。是的,很高兴知道迭代 WeakHashMap 会做什么,但如果 WeakHashMap 是考虑到问题中的其他上下文的错误方法,那么指出它作为答案也是合理的。也就是说,“缓存”这个词不足以判断 WeakHashMap 是否合适。 WeakHashMap 有弱键。如果您希望结果与键具有相同的生命周期,则适合缓存其参数是映射键(并且所述键使用身份相等)的计算。
    【解决方案4】:

    文档对此并不完全清楚,但确实这样说:

    WeakHashMap 类的行为 部分取决于 垃圾收集器,所以有几个 熟悉的(虽然不是必需的)地图 不变量不适用于此类。 因为垃圾收集器可能 随时丢弃密钥,a WeakHashMap 可能表现得好像一个 未知线程正在默默删除 条目。特别是,即使你 在 WeakHashMap 实例上同步 并且不调用它的任何修改器 方法,它是可能的大小 返回较小值的方法 时间,isEmpty 方法返回 假然后真,因为 containsKey 方法返回 true 和 后来对于给定的键为假,对于 get方法返回一个值 给定键,但后来返回 null,对于 put 方法返回 null 和 remove 方法为 a 返回 false 以前出现的钥匙 地图,并为连续 密钥集的检查,值 设置,并且条目设置为 yield 数量越来越少 元素。 - Java API

    我认为鉴于该描述,您应该期望在迭代地图时偶尔收到ConcurrentModificationExceptions。我会设计您的缓存,以便您尽可能少地进行迭代。

    【讨论】:

    • 是的,这就是我害怕的。无论如何,我并没有对其进行迭代,而是想添加一些调试工具来检查缓存中的内容,正是在那段时间里我有这个疑问。
    • 如果只是出于调试目的,您可能只需复制密钥集然后检查它。
    • 卢克,“静默线程”不是真正的线程,因此不会因为 GC 已将条目标记为(是的,实际上未删除)删除而进行并发修改。我在并发环境中使用WeakHashMap(不是缓存),没有遇到过这样的问题。
    猜你喜欢
    • 1970-01-01
    • 2015-11-13
    • 1970-01-01
    • 2013-03-19
    • 2011-08-01
    • 2012-02-19
    • 2015-12-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多