【问题标题】:Variable size LRU cache可变大小的 LRU 缓存
【发布时间】:2012-08-03 12:29:27
【问题描述】:

我正在尝试在 Java 中实现 LRU cache,它应该能够:
动态更改大小。从某种意义上说,我计划将其作为SoftReference 订阅ReferenceQueue。所以根据内存消耗,缓存大小会有所不同。

我计划使用ConcurrentHashMap,其中的值将是一个软引用,然后定期清除队列以更新地图。
但是上面的问题是,我怎么做LRU

我知道我们无法控制 GC,但是我们能否管理对值的引用(在缓存中),使得缓存中所有可能的对象都可以根据使用情况(即上次访问它的时间),而不是以某种随机方式。

【问题讨论】:

标签: java caching hashtable lru


【解决方案1】:

无论是弱引用还是软引用都不太适合这种情况。一旦对象不再有更强的引用,WeakReferences 就会立即被清除,而软引用只有在堆增长到最大大小并且需要抛出 OutOufMemoryError 否则才会被清除。

通常情况下,使用基于时间的方法和常规强引用更有效,这对于 VM 来说比 Reference 子类便宜得多(程序和 GC 处理速度更快,并且不为引用本身使用额外的内存。)。 IE。释放一段时间内未使用的所有对象。您可以使用定期 TimerTask 来检查这一点,无论如何您都需要它来操作您的参考队列。这个想法是,如果创建对象需要 10 毫秒,并且在最后一次使用后最多保留 1 秒,那么平均只比永久保留所有对象慢 1%。但由于它很可能会使用更少的内存,因此实​​际上会更快。

编辑:实现这一点的一种方法是在内部使用 3 个存储桶。放入缓存的对象总是插入到存储桶 0 中。当请求一个对象时,缓存会按顺序在所有 3 个存储桶中查找它,如果它不存在,则将其放入存储桶 0。 TimerTask 以固定的时间间隔被调用,只是丢弃桶 2 并在桶列表的前面放置一个新的空桶,这样新的桶 0 将是空的,以前的桶 0 变为 1,以前的桶 1 现在是桶2. 这将确保空闲对象将在至少一个和至多两个计时器间隔中存活,并且每个间隔访问不止一次的对象可以非常快速地检索。这种数据结构的总维护开销将大大小于基于引用对象和引用队列的所有内容。

【讨论】:

  • 我同意以下声明:TimerTask 或 Reference 线程将使用相同的时间复杂度,因为两者都必须迭代相同的元素。而且由于没有过多地使用内存,它将更加可预测和可靠。但是尺寸呢? Neo4J 也使用相同的机制 (github.com/neo4j/community/blob/master/kernel/src/main/java/org/…),他们似乎对此感到非常高兴和自豪。可能对上述所有不同技术的分析应该会使它变得更好。但总的来说,我仍然认为 Map with SoftRef 应该是最好的。
  • 如果您想通过我上面建议的实现来限制缓存对象的总数,您可以在插入新对象后达到总大小限制时简单地触发存储桶轮换并留下一个为计时器作业设置的标志,让它知道这次不做任何事情(计时器重置标志)。这将使您的缓存自动从基于时间的模式切换到大小限制模式(并返回)。如果存储桶 0 变为最大总大小的一半,您还应该启动轮换,以保持缓存的 LRU 特性并改善其最坏情况的行为。
【解决方案2】:

除非您同时需要多个这样的缓存,否则您的问题并没有真正的意义。如果你只有一个缓存,不要给它一个大小限制,总是使用WeakReference。这样,缓存将自动使用所有可用的空闲内存。

不过,请准备好与您的系统管理员进行激烈讨论,因为他们会抱怨您的应用存在内存泄漏并且“随时会崩溃!” 叹息

另一种选择是使用像EHCache 这样的成熟缓存库,因为它已经知道所有关于缓存的知识,而且他们花了数年时间才把它们弄好——从字面上看。除非您想花费数年时间调试代码以使其适用于 Java 内存模型的每个角落,否则我建议您这次避免重新发明轮子。

【讨论】:

  • 我认为我没有正确传达它。我不想在软弱之间切换。我想要的是一个带有软引用的缓存(以便它可以相应地扩展),它充当 lru 缓存。
  • 啊。然后您将不得不使用LinkedHashMap 或编写您自己的地图。 Java 中没有其他地图可以限制大小。例如,Guava 使用他们自己的 CacheBuilder 来构建具有一定限制的缓存,因为 Java 运行时没有提供足够的控制。
  • 所以基本上 LinkedHashMap 加上一些额外的努力来确定大小是最好的方法吗?
  • 这是一种花费最少但仍然返回有用的东西 + LRU 的方法。如果你可以不用 LRU,WeakHashMap 也可以。
【解决方案3】:

我会使用LinkedHashMap,因为它支持访问顺序并用作 LRU 映射。它可以有一个可变的最大尺寸。

根据使用情况在弱引用和软引用之间切换很难正确,因为。很难确定 a) 您的缓存独占使用了多少,b) 系统正在使用多少 c) 在 Full GC 之后将使用多少。

您应该注意,弱引用和软引用仅在 GC 上被清除,并且在 GC 运行之前丢弃或更改它们不会释放内存。

【讨论】:

  • 问题是:如果 LinkedHashMap,我将不得不根据总内存手动更改大小。对于最后一个陈述,我不明白为什么这应该是一个问题。
  • 如果您希望它有所作为,这只是一个问题。恕我直言,我不会增加无济于事的复杂性。如果不执行完整的 GC,则无法知道已使用的总内存,然后无法在不进行另一次 GC 的情况下强制释放已删除的条目。
  • 我认为我没有正确传达它。我不想在软弱之间切换。我想要的是一个带有软引用的缓存(以便它可以相应地扩展),它充当 lru 缓存。
  • 你能用LinkedHashMap<Key, SoftReference<Value>>吗?
  • 没有。它仍然不会使它成为LRU。但是是的,由于地图大小的限制,它确实会使对象持续更长时间(因此 lru 在特定时期内但不是永远)。但是,地图的大小仍然是一个更大的问题。 PS:为了更清楚,我已经编辑了这个问题。
猜你喜欢
  • 1970-01-01
  • 2012-03-13
  • 2011-03-12
  • 2018-09-30
  • 1970-01-01
  • 2010-12-11
  • 1970-01-01
  • 2016-10-13
  • 2011-03-02
相关资源
最近更新 更多