Java 的WeakHashMap通常被认为对缓存很有用。虽然它的弱引用是根据映射的键而不是它的值来定义的,但这似乎很奇怪。我的意思是,这是我想要缓存的值,一旦除了缓存之外没有其他人强烈引用它们,我想要收集垃圾,不是吗?
以何种方式有助于保持对键的弱引用?如果您执行 a ExpensiveObject o = weakHashMap.get("some_key")
,那么我希望缓存保持 'o' 直到调用者不再持有强引用,并且我根本不关心字符串对象“some_key”。
我错过了什么吗?
Java 的WeakHashMap通常被认为对缓存很有用。虽然它的弱引用是根据映射的键而不是它的值来定义的,但这似乎很奇怪。我的意思是,这是我想要缓存的值,一旦除了缓存之外没有其他人强烈引用它们,我想要收集垃圾,不是吗?
以何种方式有助于保持对键的弱引用?如果您执行 a ExpensiveObject o = weakHashMap.get("some_key")
,那么我希望缓存保持 'o' 直到调用者不再持有强引用,并且我根本不关心字符串对象“some_key”。
我错过了什么吗?
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>>
. 当内存紧张时, ASoftReference
将被垃圾收集。(将此与 a 进行对比WeakReference
,一旦不再有对其所指对象的硬引用,它可能会被清除。)您希望您的引用在缓存中是软的(至少在键值映射不会过时的地方) ),从那时起,如果您稍后查找它们,您的值仍有可能在缓存中。如果引用很弱,那么您的值将立即被 gc'd,从而破坏了缓存的目的。
为方便起见,您可能希望隐藏实现中的SoftReference
值Map
,以便您的缓存看起来是 type<K,V>
而不是<K,SoftReference<V>>
. 如果你想这样做,这个问题有关于网络上可用实现的建议。
另请注意,当您SoftReference
在 a 中使用值时Map
,您必须手动删除已清除的键值对SoftReferences
- 否则您Map
的大小只会永远增长,并泄漏内存。
要考虑的另一件事是,如果您采用这种Map<K, WeakReference<V>>
方法,值可能会消失,但映射不会。根据使用情况,您最终可能会得到一个包含许多弱引用已被 GC 处理的条目的 Map。
You need two maps: one which maps between the cache key and weak referenced values and one in the opposite direction mapping between the weak referenced values and the keys. And you need a reference queue and a cleanup thread.
Weak references have the ability to move the reference into a queue when the referenced object can not accessed any longer. This queue has to be drained by a cleanup thread. And for the cleanup it is necessary to get the key for a reference. This is the reason why the second map is required.
The following example shows how to create a cache with a hash map of weak references. When you run the program you get the following output:
$ javac -Xlint:unchecked Cache.java && java Cache {even: [2, 4, 6], odd: [1, 3, 5]} {even: [2, 4, 6]}
The first line shows the contents of the cache before the reference to the odd list has been deleted and the second line after the odds have been deleted.
This is the code:
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();
}
}