Java WeakHashMap经常被认为对缓存很有用。虽然它的弱引用是根据地图的键而不是它的值来定义的,但这似乎很奇怪。我的意思是,这是我想要缓存的值,除了缓存之外没有其他任何人强烈引用它们,我想要收集垃圾,不是吗?
以哪种方式保存对密钥的弱引用?如果你执行ExpensiveObject o = weakHashMap.get("some_key")
,那么我希望缓存保持为'o',直到调用者不再持有强引用,并且我根本不关心字符串对象“some_key”。
我错过了什么吗?
答案 0 :(得分:107)
WeakHashMap 不是作为缓存有用,至少是大多数人对它的看法。正如你所说,它使用弱键,而不是弱值,因此它不是为大多数人想要使用它而设计的(事实上,我已经< em>看到人们错误地使用它。
WeakHashMap主要用于保存关于其生命周期无法控制的对象的元数据。例如,如果您有一堆对象通过您的类,并且您希望跟踪有关它们的额外数据,而不需要在它们超出范围时得到通知,并且不参考它们使它们保持活动状态。
一个简单的例子(以及我以前用过的一个例子)可能是这样的:
WeakHashMap<Thread, SomeMetaData>
您可以跟踪系统中的各种线程正在做什么;当线程死亡时,该条目将从您的地图中静默删除,如果您是最后一次引用该线程,则不会使该线程被垃圾收集。然后,您可以迭代该映射中的条目,以找出有关系统中活动线程的元数据。
有关详细信息,请参阅WeakHashMap in not a cache!。
对于您所使用的缓存类型,请使用专用缓存系统(例如EHCache)或查看google-collections' MapMaker class;
之类的东西new MapMaker().weakValues().makeMap();
会做你想做的事情,或者如果你想获得幻想,你可以添加定时过期时间:
new MapMaker().weakValues().expiration(5, TimeUnit.MINUTES).makeMap();
答案 1 :(得分:32)
WeakHashMap
的主要用途是当你的键映射消失时你想要消失的映射。缓存是相反的 - 当你的值消失时,你想要消失的映射。
对于缓存,您想要的是Map<K,SoftReference<V>>
。当内存变紧时,SoftReference
将被垃圾收集。 (将其与WeakReference
进行对比,只要不再对其指示对象进行硬引用,就可以将其清除。)您希望引用在缓存中是软的(至少在键值映射中是这样的)不要过时),因为如果你以后再查找它们,你的值有可能仍然在缓存中。如果引用很弱,那么你的值将立即被gc'd,从而破坏了缓存的目的。
为方便起见,您可能希望隐藏SoftReference
实施中的Map
值,以便您的缓存显示为<K,V>
类型,而不是<K,SoftReference<V>>
。如果您想这样做,this question会对网上提供的实施提出建议。
另请注意,当您在SoftReference
中使用Map
值时,必须执行某些操作以手动删除已使用SoftReferences
的键值对清除---否则你的Map
只会永远增长,并且泄漏记忆。
答案 2 :(得分:7)
要考虑的另一件事是,如果采用Map<K, WeakReference<V>>
方法,该值可能会消失,但映射不会。根据使用情况,您最终可能会得到一个包含许多条目的地图,这些条目的弱引用已经过GC。
答案 3 :(得分:6)
您需要两个映射:一个映射在缓存键和weak referenced值之间,另一个映射在弱引用值和键之间的相反方向映射。你需要一个reference queue和一个清理线程。
弱引用能够在不再访问引用的对象时将引用移动到队列中。该队列必须由清理线程排干。 对于清理,有必要获取参考密钥。这就是为什么需要第二张地图的原因。
以下示例说明如何使用弱引用的哈希映射创建缓存。运行程序时,您将获得以下输出:
$ javac -Xlint:unchecked Cache.java && java Cache {even: [2, 4, 6], odd: [1, 3, 5]} {even: [2, 4, 6]}
第一行显示在删除对奇数列表的引用之前的缓存内容,以及删除赔率之后的第二行。
这是代码:
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Cache<K,V>
{
ReferenceQueue<V> queue = null;
Map<K,WeakReference<V>> values = null;
Map<WeakReference<V>,K> keys = null;
Thread cleanup = null;
Cache ()
{
queue = new ReferenceQueue<V>();
keys = Collections.synchronizedMap (new HashMap<WeakReference<V>,K>());
values = Collections.synchronizedMap (new HashMap<K,WeakReference<V>>());
cleanup = new Thread() {
public void run() {
try {
for (;;) {
@SuppressWarnings("unchecked")
WeakReference<V> ref = (WeakReference<V>)queue.remove();
K key = keys.get(ref);
keys.remove(ref);
values.remove(key);
}
}
catch (InterruptedException e) {}
}
};
cleanup.setDaemon (true);
cleanup.start();
}
void stop () {
cleanup.interrupt();
}
V get (K key) {
return values.get(key).get();
}
void put (K key, V value) {
WeakReference<V> ref = new WeakReference<V>(value, queue);
keys.put (ref, key);
values.put (key, ref);
}
public String toString() {
StringBuilder str = new StringBuilder();
str.append ("{");
boolean first = true;
for (Map.Entry<K,WeakReference<V>> entry : values.entrySet()) {
if (first)
first = false;
else
str.append (", ");
str.append (entry.getKey());
str.append (": ");
str.append (entry.getValue().get());
}
str.append ("}");
return str.toString();
}
static void gc (int loop, int delay) throws Exception
{
for (int n = loop; n > 0; n--) {
Thread.sleep(delay);
System.gc(); // <- obstinate donkey
}
}
public static void main (String[] args) throws Exception
{
// Create the cache
Cache<String,List> c = new Cache<String,List>();
// Create some values
List odd = Arrays.asList(new Object[]{1,3,5});
List even = Arrays.asList(new Object[]{2,4,6});
// Save them in the cache
c.put ("odd", odd);
c.put ("even", even);
// Display the cache contents
System.out.println (c);
// Erase one value;
odd = null;
// Force garbage collection
gc (10, 10);
// Display the cache again
System.out.println (c);
// Stop cleanup thread
c.stop();
}
}