如何利用可用内存在Java中高效缓存对象?

26

我需要使用Java在可用内存的一部分上缓存对象。我知道其他人也提过这个问题,但是没有一个答案符合我的需求。

我的要求是:

  • 简单而轻量
  • 不比纯HashMap明显地慢
  • 使用LRU或某种近似于LRU的删除策略

我尝试了LinkedHashMap,但它需要您指定最大元素数量,而我不知道需要多少元素才能填满可用RAM(它们的大小将有很大差异)。

我的当前方法是使用Google Collections的MapMaker如下:

Map<String, Object> cache = new MapMaker().softKeys().makeMap();

这看起来很有吸引力,因为它应该在需要更多内存时自动删除元素,然而存在一个严重的问题:它的行为是填满所有可用的内存,此时GC开始大量换页,整个应用程序的性能急剧恶化。

我听说过EHCache之类的东西,但对于我所需的东西来说似乎太重了,而且我不确定它是否足够快(记住解决方案不能比HashMap慢得多)。


你正在缓存哪些对象?我不太明白为什么你会关心缓存的性能,因为一旦你使用到过期策略,你将会比使用普通Map更加耗费资源。EHCache是一个成熟的缓存库,通过Spring进行配置也并不复杂,使用起来和Map一样简单。 - beny23
这些对象的大小从大约1kb到可能达到10kbs不等。我担心性能问题,因为从缓存中检索对象是非常CPU密集型过程的内部循环。如果速度慢,它会将我的应用程序所需的时间从几分钟增加到几个小时。 - sanity
使用softKeys()方法时,如果使用equals()方法,则无法命中缓存;只有在使用引用相等性来查找对象时,才能命中缓存。如果需要通过equals()方法进行缓存命中,请改用softValues()方法。 - Blair Zajac
可能是Java中易于使用的LRU缓存的重复问题。 - Mifeet
12个回答

8
我有与你类似的需求 - 并发(在2个六核CPU上)和LRU或类似功能 - 也尝试了Guava MapMaker。我发现softValues()比weakValues()慢得多,但当内存填满时,两者都会使我的应用程序变得极其缓慢。
我尝试了WeakHashMap,它的问题较少,奇怪的是,甚至比使用LinkedHashMap作为LRU缓存通过其removeEldestEntry()方法还要快。
但对我来说最快的是ConcurrentLinkedHashMap,它使我的应用程序比我尝试过的任何其他缓存快3-4倍!在经历了数天的挫折后,我终于找到了乐趣!它显然已经被纳入了Guava的MapMaker中,但LRU功能在Guava r07中不存在。希望它对你有用。

4

我已经实现了几个缓存,这可能和实现新的数据源或线程池一样困难,我的建议是使用jboss-cache或其他知名的缓存库。这样你就可以安心睡觉,不用担心问题。


3
我听说过EHCache这样的东西,但对于我需要的功能来说,它似乎有些过重了,而且我不确定它是否足够快(请记住,解决方案不能比HashMap慢得多)。
我真的不知道是否可以说EHCache是过重的。至少,我不认为EHCache是这样的,特别是当使用Memory Store(它由扩展的LinkedHashMap支持,并且当然是最快的缓存选项)。你应该试一试。

这个解决方案存在与LinkedHashMap相同的问题,即(来自EHCache文档的)“所有缓存在配置时指定其最大内存大小,以元素数量为单位。” - sanity
@sanity 我明白了...但是如果您不想用所有可用的内存填充缓存的对象(从而避免垃圾回收),那么我不知道如何在不限制缓存中对象数量的情况下做到这一点。您是否进行了一些代表性基准测试,以获取不同限制下的内存大小的概念? - Pascal Thivent
如果没有更好的选择,我可能最终会对其进行基准测试,但这让我感到担忧,因为现实可能以意想不到的方式与我的基准测试不同,从而引起各种问题。 - sanity
@sanity,EHCache现在似乎支持以字节为单位指定缓存大小。根据最近的EHCache发布信息(2011年11月15日):“调整缓存大小现在就像设置最大字节数一样简单。不再需要设置最大条目计数和调整驱逐参数来近似缓存可以使用的最大系统内存量。” - KajMagnus

2

我熟悉那份文件,但我意识到其中没有任何解决我的问题的内容 :-( - sanity

1

我不知道这是否是一个简单的解决方案,特别是与EHCache或类似的解决方案相比,但你是否看过Javolution库?它并非专为此而设计,但在javolution.context包中,他们有一个可重用对象而无需进行垃圾回收的分配器模式。这样,他们将对象创建和垃圾回收最小化,这对于实时编程来说是一个重要的特性。也许你应该看一看并尝试将其适应到你的问题上。


0
这个方案看起来很有吸引力,因为它应该会在需要更多RAM时自动删除元素,但是存在一个严重的问题:它的行为是填满所有可用的RAM。
使用软键只允许垃圾收集器在没有其他对象引用它们时(即当唯一引用缓存键的是缓存本身时)从缓存中删除对象。它不保证任何其他类型的驱逐。
大多数解决方案都是在Java Map类之上添加的功能,包括EhCache。
你看过commons-collections的LRUMap了吗?
请注意,对于MapMaker,存在未解决的问题,以提供LRU/MRU功能。也许你也可以在那里发表你的意见。

LRUMap要求您指定Map的最大大小,但我不知道这将是多少 - 我只希望缓存继续增长以填充未使用的RAM。 - sanity

0

在使用现有缓存时,请存储弱引用而不是普通对象引用。

如果GC开始耗尽可用空间,则弱引用所持有的值将被释放。


那会近似于LRU吗? - beny23
你正在考虑使用SoftReference - 弱引用的对象可以在任何时候被回收,而不考虑空闲内存。 - danben
请查看以下关于弱引用和软引用之间差异的讲座:http://crazybobs-talks.googlecode.com/svn/trunk/out/references.pdf 和 http://www.parleys.com/#st=5&id=2657&sl=53。 - Blair Zajac

0

过去我曾使用JCS。你可以设置配置以尝试满足你的需求。我不确定这是否能够满足你所有的要求和需求,但我在使用它时发现它非常强大。


0

你不能“删除元素”,你只能停止对它们的硬引用并等待垃圾回收器清理它们,所以继续使用Google Collections...


0

我不知道在Java中找到对象大小的简单方法。因此,我认为您不会找到一种通过占用的RAM数量来限制数据结构的方法。

基于这个假设,您只能通过缓存对象的数量来限制它。我建议运行几个真实使用场景的模拟,并收集进入缓存的对象类型的统计信息。然后,您可以计算出统计平均大小以及您可以承受缓存的对象数量。即使它只是您想要专用于缓存的RAM数量的近似值,但可能已经足够了。

至于缓存实现,在我的项目(一个性能关键的应用程序)中,我们使用EhCache,我个人并不觉得它很重量级。

无论如何,使用几种不同配置(关于大小、驱逐策略等)运行几个测试,并找出对您最有效的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接