WeakHashMap的使用
WeakHashMap的使用与HashMap类似,但是区别在于WeakHashMap对Key保留了弱引用(弱引用,四大引用类型中的一种。对于只有弱引用的对象而言,当垃圾回收进行时,无论系统内存是否足够,总会回收该对象所占用的内存),而HashMap保留了对Key的强引用。也就是说只要该HashMap存在,那么Key所引用的对象就不会被回收。而WeakHashMap的Key没有其他强引用时,这些key就可能被回收,且WeakHashMap可能会回收这些Key对应的键值对。
这里再次强调一下:WeakHashMap Weak在与Key,而非Value!
代码验证
我们通过代码来验证一下WeakHashMap的这个特性。
public class Main {
public static void main(String[] args) {
WeakHashMap<String, String> map = new WeakHashMap<>();
String value = new String("value");
map.put(new String("key"), value); // {key=value}
System.out.println(map);
System.gc();
System.runFinalization();
System.out.println(map); // {}
}
}
首先,value对象持有了对字符串对象“value”的强引用,然后将“key-value”这个字符串键值对放入map。注意这里有个小细节,就是key使用了new String() 生成,而非直接使用key的字符串常量。为什么呢?这是因为对于可在编译阶段确定的字符串,系统的字符串常量池会直接记录它,自动保留对它的强引用。此情况下,GC无法从WeakHashMap中删除这个Key。而使用new String生成的字符串对象无法在编译阶段中确定,需要在运行时生成此字符串。而且其存储位置是堆,而非字符串常量池。这样,我们通过new String()这种方式避免了对字符串“key”施加强引用。经过GC后,根据代码的输出结果,我们可以很明显的看到这个“key”被回收了,其对应的键值对也被删除。
对于下面这个情况:
public class Main {
public static void main(String[] args) {
WeakHashMap<String, String> map = new WeakHashMap<>();
String value = new String("value");
map.put("key", value);
System.out.println(map); // {key=value}
System.gc();
System.runFinalization();
System.out.println(map); // {key=value}
}
}
由于直接使用了字符串字面量“key”,造成了系统对“key”字符串的缓存,对其施加了强引用,因此GC未能销毁此实例。
再看下面这个情况,读者能看出为什么GC没有起作用吗?
class TestPool<T> {
private WeakHashMap<T, T> map = new WeakHashMap<>();
public T intern(T var) {
if (map.get(var) != null) {
return map.get(var);
}
map.put(var, var);
return var;
}
public void printMap(){
System.out.println(map);
}
}
public class Main {
private static void gc() {
System.gc();
System.runFinalization();
}
public static void main(String[] args) {
TestPool<String> testPool = new TestPool<>();
testPool.intern(new String("testPool"));
testPool.printMap(); //{testPool=testPool}
gc();
testPool.printMap(); //{testPool=testPool}
}
}
这其实是笔者模拟实现String.intern()方法的一个失败的例子。这里将同一个字符串变量即作为了键,也作为了值。作为键,它就拥有了弱引用。而同时它又是这个键值对的值,不过WeakHashMap对值是正常的强引用。因此这个键值对字符串testPool同时用了弱引用和强引用,而GC对拥有强引用的对象是不处理的,因此GC对这个WeakHashMap中特殊的键值对是束手无策的。
下面这个就可以,读者可以好好对比一下:
class TestPool_KV {
private WeakHashMap<String, MyObject> map = new WeakHashMap<>();
public MyObject intern(String key, MyObject value) {
MyObject object = map.get(key);
if (object != null) {
return object;
}
map.put(key, value);
return value;
}
public void printMap() {
System.out.println(map);
}
}
class MyObject {
}
public class Main {
private static void gc() {
System.gc();
System.runFinalization();
}
public static void main(String[] args) {
TestPool_KV pool_KV = new TestPool_KV();
pool_KV.intern(new String("name"), new MyObject());
pool_KV.printMap(); //{name=MyObject@15db9742}
gc();
pool_KV.printMap(); //{}
}
}
总结
综上所述,WeakHashMap是WeakReference在键上,而非值上。这一点我们要严加注意。