我正在尝试在Java中实现LRU cache
,它应该能够:
动态更改大小。从某种意义上说,我打算将其SoftReference
订阅ReferenceQueue
。因此,根据内存消耗,缓存大小会有所不同。
我计划使用ConcurrentHashMap
,其中值将是软参考,然后定期清除队列以更新地图。
但上面的问题是,如何使它成为LRU
?
我知道我们无法控制GC,但是我们可以管理对值(在缓存中)的引用,使得缓存中的所有可能对象将根据使用情况(在GC下)变得可以轻松访问(即它最后一次访问时而不是以某种随机的方式。
答案 0 :(得分:4)
弱引用和软引用都不适合这个。只要对象没有更强的引用就会立即清除WeakReferences,并且只有在堆已经增长到最大大小并且需要抛出OutOufMemoryError时才会清除软引用。
通常使用一些基于时间的方法和常规的强引用效率更高,这些引用对于VM来说比参考子类便宜得多(对于程序和GC来说处理速度更快,并且对于引用本身不使用额外的内存)。即释放在特定时间内未使用的所有对象。您可以使用定期TimerTask来检查这一点,无论如何都需要它来操作您的参考队列。我们的想法是,如果创建对象需要10毫秒,并且在最后一次使用后保持最多1秒,那么平均仅比永久保留所有对象的速度慢1%。但由于它很可能会使用更少的内存,因此它实际上会更快。
编辑:实现此目的的一种方法是在内部使用3个存储桶。放入缓存中的对象始终插入到存储桶0中。当请求对象时,缓存按顺序在所有3个存储桶中查找它,如果它尚未存在则将其放入存储桶0中。 TimerTask以固定的间隔调用,只丢下桶2并在桶列表的前面放置一个新的空桶,这样新桶0将为空,前桶0为1,前桶1为桶2.这将确保空闲对象至少存活一个且最多两个计时器间隔,并且每个间隔访问多次的对象检索速度非常快。这种数据结构的总维护开销将远远小于基于引用对象和引用队列的所有内容。
答案 1 :(得分:1)
除非你想要同时使用其中几个缓存,否则你的问题确实没有意义。如果您只有一个缓存,请不要给它一个大小限制,并始终使用WeakReference
。这样,缓存将自动使用所有可用的可用内存。
准备与您的系统管理员进行一些热烈的讨论,因为他们会抱怨您的应用程序内存泄漏并且“随时都会崩溃!” 叹息
另一种选择是使用像EHCache这样的成熟缓存库,因为它已经知道了有关缓存的所有知识,并且他们花了数年时间才能正确地实现它们。除非您想花费数年时间调试代码以使其适用于Java内存模型的每个角落情况,否则我建议您不要再次重新发明轮子。
答案 2 :(得分:0)
我会使用LinkedHashMap,因为它支持访问顺序并用作LRU映射。它可以有一个可变的最大大小。
根据使用情况在弱引用和软引用之间切换很难正确,因为。很难确定a)你的缓存使用了多少,b)系统使用了多少c)完整GC后将使用多少。
您应该注意,仅在GC上清除弱引用和软引用,并且在GC运行之前丢弃它们或更改它们将不会释放内存。