在上一篇文章中介绍了Java的四种对象引用,这篇文章介绍一个java中相关的具体应用WeakHashMap。
WeakHashMap是基于弱引用实现的哈希表,与HashMap在操作上基本相同,WeakHashMap最大的特点是对于哈希表中的每个Key,如果除了自身对Key有引用外,此Key没有其他引用,那么该Key对应的键值对会被表自动移除,这个行为取决于垃圾回收器的动作,只有在垃圾回收器清除该Key的弱引用之后,该键值对才会被自动移除。
示例1:
public class test {
public static void main(String[] args) throws InterruptedException {
Map<String, String> weakHashMap=new WeakHashMap<String,String>();
String str=new String("test");
weakHashMap.put(str,"1");
System.out.println("size1:"+weakHashMap.size());
str=null;
System.gc();
Thread.sleep(2000);
System.out.println("size2:"+weakHashMap.size());
}
}
程序输出结果
size1:1
size2:0
当我们将str置为null时,此时仅有weakHashMap维持有对new String(“test”)这个对象的引用,之后weakHashMap就会移除该键值对,那么weakHashMap的移除过程是如何触发的呢?我们可以一步一步的测试一下。
示例2:
public class test {
public static void main(String[] args){
List<WeakHashMap<byte[][],byte[][]>> mapList=new ArrayList<WeakHashMap<byte[][],byte[][]>>();
int len=1024*10;
for(int i=1;i<1000;i++){
WeakHashMap<byte[][],byte[][]> map=new WeakHashMap<byte[][],byte[][]>();
map.put(new byte[len][len],new byte[len][len]);
mapList.add(map);
System.gc();
System.out.println(i);
}
}
}
在我本机上运行实例2,循环到第22次就会报OutOfMemoryErro(如果你的没有报这个错误,可以考虑把len值调大一些),这说明我们申请的堆没有及时得到释放,你可能会感到疑惑,我们明明已经调用了System.gc(),weakHashMap会自动移除没有其他引用的键值对,该键值对关联的堆就会得到释放,我们再次申请分配堆的时候应该有足够的堆资源,怎么还会报这个错误呢?我们再看下个例子。
示例3:
public class test {
public static void main(String[] args){
List<WeakHashMap<byte[][],byte[][]>> mapList=new ArrayList<WeakHashMap<byte[][],byte[][]>>();
int len=1024*10;
for(int i=1;i<1000;i++){
WeakHashMap<byte[][],byte[][]> map=new WeakHashMap<byte[][],byte[][]>();
map.put(new byte[len][len],new byte[len][len]);
mapList.add(map);
System.gc();
//增加的代码
for(WeakHashMap tmp:mapList){
tmp.size();
}
System.out.println(i);
}
}
}
运行实例3,我们可以发现不会报错了,循环能够正常结束,说明申请的堆得到了及时的释放,与实例2相比实例3对已经在列表中的WeakHashMap调用了size()这一个方法,我们看一下WeakHashMap的size方法源码
public int size() {
if (size == 0)
return 0;
expungeStaleEntries();
return size;
}
在这个方法中调用了expungeStaleEntries()方法
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
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) {
Entry<K,V> next = p.next;
if (p == e) {
if (prev == e)
table[i] = next;
else
prev.next = next;
// Must not null out e.next;
// stale entries may be in use by a HashIterator
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}
而这个正是WeakHashMap移除除键值所调用的方法,只不过当你启动垃圾回收器之后这个方法并不会自动触发,只有接下来对WeakHashMap有进行操作才会触发,翻看WeakHashMap的源码,你会发现只有size(),resize(),getTable()这个三个方法中调用了expungeStaleEntries()方法,而这三个方法在WeakHashMap的所有操作方法中至少有一个会调用。
我们开头说WeakHashMap是基于弱引用的,这个弱引用就体现在Key值上,我们查看WeakHashMap的键值对存储结构Entry<K,V>
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final int hash;
Entry<K,V> next;
/**
* Creates new entry.
*/
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<K,V>继承至弱引用WeakReference<Object>,每次构造一个新的Entry时,Key值对应的对象都会被构造为一个WeakReference,这样当外部没有对Key值的引用时,该Key指向的对象资源便会被回收,并将弱引用放到ReferenceQueue中,注意这里也仅仅只是回收Key指向的对象资源,而键值对的删除以及Value所指向对象的处理都在上面提到的expungeStaleEntries函数里。