什么是WeakHashMap?
WeakHashMap是以及基于java弱引用实现的HashMap,感觉一句话就讲的差不多了嘿嘿。
先看看它的定义:
public class WeakHashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>
如上,在定义方面和HashMap并没有什么不同。而且在结构上,基本和HashMap一致。在Java8中,唯一存储上的不同点就是,当冲突的key变多时,HashMap引入了二叉树(红黑树)进行存储,而WeakHashMap则一直使用链表进行存储。
而WeakHashMap的特点,这里也有总结:
- 基于map接口,是一种弱键相连,WeakHashMap里面的键会自动回收
- 支持 null值和null键。和HashMap有些相似
- fast-fail机制
- 不允许重复
WeakHashMap经常用作缓存
关于HashMap的具体存储结构,这里就不细讲了,大家可以看我这篇深入分析的博客:集合源码学习:HashMap
Java里面引用分为4中类型,大点分有强,弱,
大家可以参阅我这篇文章结合源码详细介绍4种引用。Java里面引用分类
而在WeakHashMap则主要用到了WeakReference这个引用,这篇文章就主要分析,为什么WeakHashMap具有如此特性(主要是Weak)。
关于Entry<K,V>
和HashMap一样,WeakHashMap也是用一个Entry实体来构造里面所有的元素的,但是这个Entry却和HashMap的不同,他是弱引用。
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>
如上,Entry还继承了WeakReference,所以Entry是个弱引用。何为弱引用呢?就是就是每当进行一次gc,你这个对象就会被清除,当然如果这个对象还存在着软引用或者强引用,就可能不会被清除。
ReferenceQueue queue作用
queue是用来存放那些,被jvm清除的entry的引用,因为WeakHashMap使用的是弱引用,所以一旦gc,就会有key键被清除,所以会把entry加入到queue中。在WeakHashMap中加入queue的目的,就是为expungeStaleEntries所用。
Entry(Object key, V value,
ReferenceQueue<Object> queue,
int hash, Entry<K,V> next) {
super(key, queue);
this.value = value;
this.hash = hash;
this.next = next;
}
在构造每一个Entry时,都将它与queue绑定,从而一旦被jvm回收,那么这个Entry就会倍加如到queue中。
expungeStaleEntries方法具体意思
这里面我的理解和很大一部分的博客都不同,我认为在这个方法里面就仅仅是释放value值。由前面的Entry的构造方法可知, super(key, queue);
传入父类的仅仅是key,所以经过仔细阅读jdk源码开始部分分析后,得出结论,在WeakHashMap中,有jvm回收的,仅仅是Entry的key部分,所以一旦jvm强制回收,那么这些key都会为null,再通过私有的expungeStaleEntries
方法,把value也制null,并且把size--
。
首先看代码:
/**
* 从ReferenceQueue中取出过期的entry,从WeakHashMap找到对应的entry,逐一删除
* 注意,只会把value置为null。
*/
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
//遍历queue
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) x;
int i = indexFor(e.hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> p = prev;
while (p != null) {
//遍历table[i]所在链表
Entry<K,V> next = p.next;
if (p == e) {
//queue里面有e,那就删了。
if (prev == e)
//e就是当前的p.next
table[i] = next;
else
prev.next = next;
// Must not null out e.next;
// stale entries may be in use by a HashIterator
//置为null,帮助gc。只制null了value。
e.value = null; // Help GC
//设置e的value,但是没看到设置e的key。
size--;
break;
}
prev = p;
p = next;
}
}
}
}
上面代码逻辑为,当在table中找到queue中存在元素时,就把value制空,然后size--
。所以在WeakHashMap中,就只有key被回收。下面看一个实例验证。
首先需要了解一点:expungeStaleEntries方法在哪些方面会被调用?
经过阅读源码,发现expungeStaleEntries方法只在一下几个地方被调用:
private Entry<K,V>[] getTable()
里面,而这个getTable则在下列方法被调用:public V get(Object key)
Entry<K,V> getEntry(Object key)
public V put(K key, V value)
void resize(int newCapacity)
public V remove(Object key)
boolean removeMapping(Object o)
public boolean containsValue(Object value)
private boolean containsNullValue()
public void forEach(BiConsumer<? super K, ? super V> action)
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
public int size()
void resize(int newCapacity)
大致了解了上面调用情况后,现在看一段测试代码。
测试:
public static void main(String[] args) throws InterruptedException {
WeakHashMap<String, String> map = new WeakHashMap<String, String>();
for(int i = 0;i < 5;i++){
map.put(new String("字符串"+i), new String("串串"+i));
}
System.gc();
TimeUnit.SECONDS.sleep(2);
System.out.println(map.size());
map.put(new String("字符串"+6), new String("串串"+6));
}
当我在WeakHashMap源码的size方法里面打一个断点时候:
之后,程序直接掉进这里面,从这里面可以看出size大小以及table详情看下图:
所以,从上面可以看出,WeakHashMap中,key会被gc,而value,则是通过expungeStaleEntries赋值为null。
疑问
验证我的思维过程中,遇到了一个很神的问题:
我在测试过程中,真的是测了好多次代码,才发现这样可以得到我所推理出的情况,但是,当我把断点打到上面测试代码第8行时,size为0,并且table也完全为空。
后来我发现,不管在哪里打,只要你在你测试类打,然后再跳到源码去看,size还是为0,甚至我在expungeStaleEntries里面加一个断点,却无法被调用。
本来尝试把整个jdk项目放到eclipse里满去里面查找,但是内存耗不起,加载半天加载不出来。
如果哪位老哥遇到过或者解决过这个问题,请在下方留言帮助我解答这个疑惑吧,感激不尽。
花了好些天学习WeakHashMap,在学习中除了看源码也从下列博客中得到了很多知识:
http://blog.csdn.net/u013256816/article/details/50907595
http://blog.csdn.net/u013256816/article/details/50916504
http://www.cnblogs.com/selfchange/p/7154909.html