天天看點

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回收,釋放空間。