我想要一个具有N的最大保留容量的缓存。我允许它容纳N个对象,否则这些对象将符合GC的条件。现在,如果我的应用程序本身当前拥有对先前添加到缓存中的对象的N + 1个强引用,我希望缓存也保持N + 1。为什么?因为缓存不会使这个第N + 1个对象被收集的时间比其它方式更长,而且我可以使用更大的哈希表进行更多的缓存命中。
另一种放置它的方法,我想要一个对象缓存,它保留所有添加到它的对象,同时它们保持强烈可达,并且还保留足够的非强可达对象以保持其大小== N.
我们创建了一个N = 100的缓存。大小从0开始。添加了150个对象,大小为150.这些对象中的100个变得非强烈可达(弱,轻柔,无论如何)。缓存驱逐其中的50个并保持50,大小为100.添加了49个更强的可达对象。大小仍然是100,但现在其中99个是强可达的,只有一个是非强可达的。发生的事情是49个较旧的,非强烈可达的对象被新的49替换,因为新的对象很容易到达。
我怀疑对于许多用例来说,这实际上是一个直观的事情。通常,缓存的容量会根据缓存命中概率进行折衷,以保证最大内存使用率。知道它所拥有的对象的可达性,缓存可以提供更高的缓存命中概率,而不会改变其最大内存使用保证。
我担心JVM上不可能。我希望以其他方式被告知,但如果你知道一个事实,那么如果有理由我也不会接受这个答案。
答案 0 :(得分:2)
您可以将条目添加到配置为LRU或FIFO缓存的LinkedHashMap。你也可以有一个WeakHashMap。如果将密钥添加到两个映射中,LHM将阻止清除,即使它在WHM中。一旦LHM丢弃该密钥,它可能会或可能不在WHM中。
e.g
private final int retainedSize;
private final Map<K,V> lruMap = new LinkedHashMap<K, V>(16, 0.7f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > retainedSize;
}
};
private final Map<K,V> weakMap = new WeakHashMap<K, V>();
public void put(K k, V v) {
lruMap.put(k, v);
weakMap.put(k,v);
}
public V get(K k) {
V v = lruMap.get(k);
return v == null ? weakMap.get(k) : v;
}
这样做的原因之一是WeakHashMap一下子就更清晰了,所以你的命中率会急剧下降。这种方法可以确保在您使用完整GC后,当您尝试赶上时,您的性能不会下降太多。 ;)
答案 1 :(得分:0)
结帐WeakHashMap
。陈旧的引用将被自动删除。在放置之前,您可以检查尺寸是否超过您的阈值并跳过添加新值。
Alternativley你可以覆盖put并丢弃该值,如果大小超过你的阈值。
此方法可以按照您的建议运行,因为您不需要缓存逐出策略,如果大小超过阈值,您可以跳过添加新元素。
答案 2 :(得分:0)
我认为你想要的东西是有意义的,但可能没那么多。让我们假设值非常大(几千字节),否则其他地方的缓存强烈保持值也可能变得昂贵。忽略这种开销,您的缓存确实具有恒定的内存成本。但是,我不确定这个目标是否值得追求 - 我很感兴趣如何使用整个程序的恒定内存量(我不想留下太多未使用的内存,在任何情况下我都不想要开始交换)。
想法:缓存应该使用注册的弱(或软)引用。 1 你在循环中使用另一个调用ReferenceQueue.remove()
的线程并检查某些条件 2 。根据它,您可以从缓存中删除相应的条目(如Guava所做的那样),或者通过reference.get()
恢复该值,从而暂时保护它不被垃圾收集。 3 。这应该有效,但每次GC运行都需要一些时间。
1 覆盖finalize()
也可以。 实际上,看起来这是reference.get()
排队时唯一的方式,因为它总是返回null,因此不能用于复活。
2 条件应该是“每次GC运行100次”。
3 我不确定GC是否真的以这种方式工作,但我认为确实如此。如果没有,那么您可以使用该值的副本。我也不确定当下一次该值失去强可达性时会发生什么,但同样,这肯定是可以解决的(例如,创建一个新的参考)。