Java的WeakHashMap通常被认为对缓存有用。尽管其弱引用是根据地图的键而不是其值定义的,但这似乎很奇怪。我的意思是,这是我要缓存的值,而一旦缓存中没有其他人强烈引用它们,我想获取垃圾回收值,不是吗?
以哪种方式保留对键的弱引用?如果执行a ExpensiveObject o = weakHashMap.get("some_key")
,那么我希望高速缓存保持在'o'状态,直到调用者不再拥有强引用为止,并且我根本不在乎字符串对象“ some_key”。
我想念什么吗?
Java的WeakHashMap通常被认为对缓存有用。尽管其弱引用是根据地图的键而不是其值定义的,但这似乎很奇怪。我的意思是,这是我要缓存的值,而一旦缓存中没有其他人强烈引用它们,我想获取垃圾回收值,不是吗?
以哪种方式保留对键的弱引用?如果执行a ExpensiveObject o = weakHashMap.get("some_key")
,那么我希望高速缓存保持在'o'状态,直到调用者不再拥有强引用为止,并且我根本不在乎字符串对象“ some_key”。
我想念什么吗?
Answers:
WeakHashMap不能用作缓存,至少在大多数人看来是这样。如您所说,它使用的是弱键而不是弱的值,因此它并不是针对大多数人想要使用的功能而设计的(事实上,我见过人们不正确地使用它)。
WeakHashMap最有用的是保留有关您无法控制其生命周期的对象的元数据。例如,如果您有一堆通过类的对象,并且您希望跟踪有关它们的额外数据,而无需在它们超出范围时得到通知,并且无需引用它们就可以使它们保持活动状态。
一个简单的示例(以及我之前使用过的示例)可能类似于:
WeakHashMap<Thread, SomeMetaData>
您可以在哪里跟踪系统中的各个线程在做什么;当线程死亡时,该条目将从您的映射中静默删除,并且如果您最后引用该线程,则不会阻止该线程被垃圾回收。然后,您可以遍历该映射中的条目,以查找有关系统中活动线程的元数据。
在非缓存中查看WeakHashMap!欲获得更多信息。
对于您想要的缓存类型,请使用专用的缓存系统(例如EHCache)或查看Guava的MapMaker类;就像是
new MapMaker().weakValues().makeMap();
将执行您想要的操作,或者如果您想花哨的话,可以添加定时到期时间:
new MapMaker().weakValues().expiration(5, TimeUnit.MINUTES).makeMap();
主要用途WeakHashMap
是当您具有要在其键消失时消失的映射。缓存是相反的-您具有要在其值消失时消失的映射。
对于缓存,您想要的是一个Map<K,SoftReference<V>>
。SoftReference
内存不足时,A将被垃圾回收。(将此与进行对比WeakReference
,一旦不再有对其硬引用就可以清除它。)您希望您的引用在缓存中保持软化(至少在键值映射不陈旧的缓存中) ),从那时起,如果以后再查找它们,则您的值就有可能仍在缓存中。如果引用比较弱,那么您的值将立即被gc'击败,从而达到缓存的目的。
为了方便起见,您可能希望隐藏实现中的SoftReference
值Map
,以使缓存看起来是<K,V>
而不是的类型<K,SoftReference<V>>
。如果您想这样做,此问题对网络上的可用实现提出了建议。
还请注意,当您在中使用SoftReference
值时Map
,您必须执行一些操作以手动删除已SoftReferences
清除了它们的键值对-否则,您的Map
大小将永远增长,并泄漏内存。
Map<K, SoftRereference<?>>
方法会SoftReference
在包含null
引用对象的映射中留下实例。我认为,此映射的内部实现必须定期清除所有映射,并使用一个值(该值是包含引用null
对象的软引用)来进行良好的清理。
HashMap<K, SoftReference<V>>
则将导致内存泄漏。您可能会考虑将其包含在您的答案中。看一下WeakHashMap
它是如何进行的,Oracle JDK中有一个私有方法expungeStaleEntries
来处理此清理工作。
要考虑的另一件事是,如果采用此Map<K, WeakReference<V>>
方法,则值可能会消失,但映射不会消失。根据使用情况,您可能最终会得到一个包含许多条目的地图,这些条目的弱引用已被GC处理。
Map<K, V>
不是Map<K, WeakReference<V>>
。从表面上看,这个答案似乎是有道理的,但是请注意,每次用户调用时Map.get
,都可以删除丢失的映射,并且看到这正是WeakHashMap删除键的方式,这不可能是Java团队并未意识到这一点。 。
您需要两个映射:一个映射在高速缓存键和弱引用值之间映射,一个映射在相反方向上在弱引用值和键之间映射。您需要一个参考队列和一个清理线程。
当被引用的对象无法再访问时,弱引用可以将其移入队列。此队列必须由清理线程清除。 为了进行清理,有必要获取密钥以供参考。 这就是为什么需要第二张地图的原因。
以下示例显示如何使用弱引用的哈希映射创建缓存。运行程序时,将得到以下输出:
$ javac -Xlint:未选中Cache.java && Java缓存 {偶数:[2,4,6],奇数:[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();
}
}