天天看点

WeakHashMap实现及使用场景目的用途实现方式代码详解实例解析:tomcat中缓存

目的

让Map中不再使用的Entry被GC及时回收,释放内存空间

用途

Map本身生命周期很长,需要长期贮留内存中,但Map中的Entry可以删除,使用时可以从其它地方再次取得。

实例:tomcat中的缓存有用到。

实现方式

对于WeakReference可以参考另一篇文章:弱引用——WeakReference——所引用的对象的回收规则

  1. 在WeakHashMap类中定义了一个实例域ReferenceQueue<Map.Entry> queue。
  2. 内部的Entry直接继承了WeakReference,Entry中没有定义key字段,而是调用super(key,queue),将 key 保存在Reference类的referent字段中。
  3. 由于Entry本身对key是弱引用,因此GC会监测key,在某个Entry的key处于适当状态时,Entry会被加入到pending列表,然后由ReferenceHandler将Entry添加到queue队列。
  4. WeakHashMap中的许多操作,比如get(K key),size(),remove(K key)时,都会先调用expungeStaleEntries();方法,这个方法会将已经被添加到queue中的Entry从map中移除,同时会将entry的value变量的值置为null。
  5. 经过步骤4,entry被从Map中移除后,不再有对此entry的引用,entry对key即referent的引用是弱引用,entry的value的值被赋值为null,原来的value的对象也不再被引用。GC就可以回收这些对象了。

代码详解

  1. 自定义的内部类Entry<K,V>,实现了Map.Entry<K,V>,同时继承了WeakReference。但并非堆整个Entry做弱引用,而是对key做弱引用。也就是说,WeakHahsMap中的每个Entry都是一个弱引用,table数组并不是直接引用Entry对象,而是引用了一个引用了Entry对象的弱引用实例。

    可以看到代码中,没有定义实例域key,而是调用WeakReference的构造函数super(key,queue),使得本弱引用对象的referent变量指向了key。

    而getKey()方法,则是调用WeakReference的get()方法,返回referent的值。

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;
        }

	@SuppressWarnings("unchecked")
        public K getKey() {
            return (K) WeakHashMap.unmaskNull(get());
        }
	
        public int hashCode() {      /* 重写实现了Object类中的hashCode,此方法是计算整个Entry实例对象的hashCode,不是计算key的hashCode */
            K k = getKey();
            V v = getValue();
            return Objects.hashCode(k) ^ Objects.hashCode(v);
        }
}
           
  1. 类中定义了一个声明的同时也初始化了的ReferenceQueue类型的变量:

    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

    WeakHashMap中的所有Entry的key都会在super(key,queue)时,注册到此queue上。

    GC线程会监测这些key的可达性的状态,在key处于一个特殊状态时,就会将引用key的WeakReference实例对象的状态设置为pending,并将WeakReference实例添加到pengding列表中去。

    而Reference类创建的ReferenceHandler线程则会自旋处理pending列表中的所有处于pending状态的Reference实例,将它们enqueue()到queue中去,

    最终GC会回收queue里的所有Reference实例,由于是Entry实现了WeakReference,因此最终是整个entry被回收。

  2. 获取WeakHashMap的table[]数组时,会将已经被GC入队的key关联的entry从map中删除。
private Entry<K,V>[] getTable(){
	Entry<K,V>[] table = expungeStaleEntries();
	return table;
}

/**
     * Expunges stale entries from the table.
     */
    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;
                }
            }
        }
    }
           
  1. get(K key)时,会调用Reference的get()获得Entry真正的key,与参数key做比较
public V get(Object key) {
        Object k = maskNull(key);
        int h = hash(k);
        Entry<K,V>[] tab = getTable();
        int index = indexFor(h, tab.length);
        Entry<K,V> e = tab[index];
        while (e != null) {
            if (e.hash == h && eq(k, e.get()))
                return e.value;
            e = e.next;
        }
        return null;
    }
           

实例解析:tomcat中缓存

public final class ConcurrentCache<K,V>{

	private  final int size;

	private  final Map<K,V> eden;    //新创建的,最近使用的,放在eden里。
	private final Map<K,V> longTerm;    // 当eden满了后,将eden里的所有对象移动到longTerm里。

	public ConcurrentCache(int  size){
		this.size=size;
		eden= new ConcurrentHashMap(size);
		longTerm = new WeakHashMap();
	}

	public V get(K k){     /* 被get,最新被使用了,必须要在eden中*/
		V v = eden.get(k) ;
		if(v==null){
			synchronized(longTerm){
			  v = longTerm.get(k);			
			}
			if(v!=null){
				eden.put(k,v);
			}		    
		}
		return v;	
	}

	public V put(K K,V v){   /*最新创建的,放到eden中*/
		if(eden.size()>=size){
			synchronized(longTerm){
				longTerm.putAll(eden);
			}
			eden.clear();
		}
		eden.put(k,v);
	
	}
}
           

get时,如果eden中没有,而longTerm中有,则将数据取出后,再添加到eden中,保证最新最近使用的放在eden中。

put时,如果eden已经满了,就将eden中的全部倒换到longTerm中去,将新创建的这个要put的放到eden中。

如此,longTerm中就是长期未使用的、不常用的,因此用WeakHashMap以便GC回收,释放空间。