【问题标题】:Java fixed memory mapJava 固定内存映射
【发布时间】:2011-02-25 20:15:51
【问题描述】:

是否有一个简单、高效的Map 实现允许对地图使用的内存进行限制。

我的用例是我想在创建时动态分配大部分可用内存,但我不想在将来的任何时候使用OutOFMemoryError。基本上,我想将此地图用作缓存,但我想避免像EHCache 这样的大量缓存实现。我的需求很简单(最多一个LRU算法)

我应该进一步澄清,我的缓存中的对象是char[] 或类似的原语,它们不会包含对其他对象的引用。

我可以为每个条目设置最大大小的上限。

【问题讨论】:

  • 根据我对您问题的理解,您的想法是在启动时分配专用的内存量用于缓存。这样做的问题是,您无法提前知道内存的类型(我们在这里谈论的是缓存对象,对吗?)。与 C 不同,您不能简单地分配一个内存块,然后将指针转换为要被视为对象 X 的子部分。因此,对于通用解决方案,固定内存缓存在 Java 中不实用。此外,您认为这可以保护您免受 OutOfMemoryErrors 的影响充其量是值得怀疑的。

标签: java caching dictionary memory-management fixed


【解决方案1】:

您可以使用LinkedHashMap 来限制Map 中的条目数:

removeEldestEntry(Map.Entry<K,V> eldest):如果此映射应删除其最旧的条目,则返回 true。在向映射中插入新条目后,putputAll 调用此方法。它为实现者提供了在每次添加新条目时删除最旧条目的机会。如果映射表示缓存,这很有用:它允许映射通过删除过时的条目来减少内存消耗。

示例使用:此覆盖将允许映射增长到 100 个条目,然后在每次添加新条目时删除最旧的条目,保持 100 个条目的稳定状态。

private static final int MAX_ENTRIES = 100;

protected boolean removeEldestEntry(Map.Entry eldest) {
    return size() > MAX_ENTRIES;
}

相关问题

【讨论】:

  • 一旦没有对键的强引用,WeakHashMap 将删除该条目。这听起来不太适合缓存。此外,如果足够强的引用被保持在密钥上,您仍然会看到 OOME。所以我不明白 WeakHashMap 如何在这里提供帮助..
  • @Zwei:我只是把它留在那里作为可能;我现在已经删除了它,只保留了LinkedHashMap 作为一个选项。
  • @juber:相关问题之一提到org.apache.commons.collections.map.LRUMap;你可以检查一下。我实际上并不熟悉它。
【解决方案2】:

对于缓存,SoftHashMapWeakHashMap 更合适。 WeakhashMap 通常用于当您希望与该对象保持关联时,只要该对象处于活动状态,但不阻止它被回收。

相比之下,SoftReference 与内存分配的关系更密切。有关差异的详细信息,请参阅No SoftHashMap?

WeakHashMap 通常也不合适,因为它与错误的缓存方式相关联——它使用弱键和硬值。也就是说,当 key 被垃圾收集器清除时,key 和 value 将从映射中删除。这通常不是您想要的缓存 - 其中键通常是轻量级标识符(例如字符串或其他一些简单的值类型) - 缓存通常操作使得键/值在 value时被回收> 引用被清除。

Commons Collections 有一个ReferenceMap,您可以在其中插入您希望用于键和值的引用类型。对于内存敏感的缓存,您可能会对键使用硬引用,对值使用软引用。

要获得给定数量 N 的 LRU 语义,请维护从缓存中获取的最后 N 个条目的列表 - 当从缓存中检索到条目时,它被添加到列表的头部(以及尾部列表已删除。)为确保这不会占用太多内存,您可以创建一个软引用并将其用作触发器,以从列表末尾逐出一定百分比的条目。 (并为下一个触发器创建一个新的软引用。)

【讨论】:

    【解决方案3】:

    Java 平台解决方案

    如果您要查找的只是一个可以清理其键以避免OutOfMemoryErrors 的映射,您可能需要查看WeakHashMap。它使用WeakReferences 以允许垃圾收集器获取映射条目。但是,它不会强制执行任何类型的 LRU 语义,除了分代垃圾回收中存在的那些。

    还有LinkedHashMap,文档中有这个:

    提供了一个特殊的构造函数 创建一个链接的哈希映射,其顺序 迭代的顺序是它的 上次访问的条目,来自 最近最少访问 最近(访问顺序)。这 一种地图非常适合建造 LRU 缓存。调用 put 或 get 方法导致访问 相应的条目(假设它 调用后存在 完成)。 putAll 方法 为每个生成一个条目访问 在指定的映射中映射,在 键值映射的顺序 由指定地图的条目提供 设置迭代器。没有其他方法 生成入口访问。在 特别是,操作 集合视图不影响 支持映射的迭代顺序。

    因此,如果您使用此构造函数创建一个其Iterator 在 LRU 中迭代的映射,则修剪该映射变得非常容易。一个(相当)需要注意的是,LinkedHashMap 根本不同步,因此您需要自己处理并发问题。您可以将其包装在同步包装器中,但这可能会产生吞吐量问题。

    推出您自己的解决方案

    如果我必须为此用例编写自己的数据结构,我可能会创建某种数据结构,其中包含映射、队列和 ReadWriteLock 以及一个看门人线程来处理太多时的清理条目在地图中。可能会稍微超过所需的最大尺寸,但在稳定状态下你会保持在它之下。

    【讨论】:

    【解决方案4】:

    WeakHashMap 不一定能达到您的目的,因为如果您的应用程序持有足够强的密钥引用,您将看到 OOME。

    或者,您可以查看SoftReference,一旦堆稀缺,它将清除内容。但是,我看到的大多数 cmets 都表明它不会使引用无效,直到堆真的非常低并且大量 GC 开始启动并严重影响性能(所以我不建议将它用于您的目的) .

    我的建议是使用简单的 LRU 映射,例如http://commons.apache.org/collections/apidocs/org/apache/commons/collections/LRUMap.html

    【讨论】:

      【解决方案5】:

      谢谢大家的回复!

      正如 jasonmp85 指出的那样,LinkedHashMap 有一个允许访问顺序的构造函数。当我查看 API 文档时,我错过了这一点。该实现看起来也很有效(见下文)。结合每个条目的最大尺寸上限,应该可以解决我的问题。

      我还将仔细研究 SoftReference。仅作记录,Google Collections 似乎对 SoftKeys 和 SoftValues 和 Maps 有很好的 API。

      这是一个来自 Java LikedHashMap 类的 sn-p,展示了它们如何维护 LRU 行为。

          /**
           * Removes this entry from the linked list.
           */
          private void remove() {
              before.after = after;
              after.before = before;
          }
      
          /**
           * Inserts this entry before the specified existing entry in the list.
           */
          private void addBefore(Entry<K,V> existingEntry) {
              after  = existingEntry;
              before = existingEntry.before;
              before.after = this;
              after.before = this;
          }
      
          /**
           * This method is invoked by the superclass whenever the value
           * of a pre-existing entry is read by Map.get or modified by Map.set.
           * If the enclosing Map is access-ordered, it moves the entry
           * to the end of the list; otherwise, it does nothing.
           */
          void recordAccess(HashMap<K,V> m) {
              LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
              if (lm.accessOrder) {
                  lm.modCount++;
                  remove();
                  addBefore(lm.header);
              }
      

      【讨论】:

      • 显然是的。那好吧。至少我习惯于在我所在的线程上投票给其他人(假设他们的答案很有帮助,而且不是非常不正确)。
      • 对不起,这是我第一次在 StackOverflow 上发布问题。他们不让我投票,因为我没有注册。我接受了自己的答案,因为我在那里总结了我认为有帮助的答案。乔森,是的,你的回答很有帮助,谢谢。
      猜你喜欢
      • 2014-08-01
      • 2011-02-08
      • 2011-05-14
      • 2011-08-21
      • 2014-04-04
      • 1970-01-01
      • 2020-01-05
      • 2010-11-04
      • 1970-01-01
      相关资源
      最近更新 更多