1. 程式人生 > >弱引用總結及HashMap、List弱引用並Lru實現

弱引用總結及HashMap、List弱引用並Lru實現

Reference

C++中有指標和Reference的概念,指標可以重新賦值,而Reference只能初始化時賦值。

然而,java中的Reference是可以重新賦值,並不是C++的Reference概念,類似於C++的指標的概念。

WeakReference和Strong Reference

通常例項化的操作就是強引用:

Object obj = new Object();

obj強引用new Object()在堆上分配的空間。

只有執行obj=null; 時,new Object()在堆上分配的記憶體才會失去引用,可以被GC回收。

弱引用宣告:

WeakReference<Object> ref_obj = new WeakReference<Object>(new Object);

這樣ref_obj弱引用new Object()在堆上分配的空間。

當GC被觸發時,就會回收或者稍後回收heap空間。

HashMap(put方法)新增物件引用

@Override public V put(K key, V value) {
        if (key == null) {
            return putValueForNullKey(value);
        }

        int hash = Collections.secondaryHash(key);
        HashMapEntry<K, V>[] tab = table;//---------------------HashMap資料存於HashMapEntry陣列內。
        int index = hash & (tab.length - 1);
        for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
            if (e.hash == hash && key.equals(e.key)) {
                preModify(e);
                V oldValue = e.value;
                e.value = value;//-----------------------Heap Object賦值給HashMapEntry的value變數。
                return oldValue;
            }
        }

        // No entry for (non-null) key is present; create one
        modCount++;
        if (size++ > threshold) {
            tab = doubleCapacity();
            index = hash & (tab.length - 1);
        }
        addNewEntry(key, value, hash, index);
        return null;
    }

static class HashMapEntry<K, V> implements Entry<K, V> { //----------實現Entry介面,並沒有WeakReference。
        final K key;
        V value;
        final int hash;
        HashMapEntry<K, V> next;
.... ...

綜上,可以看出,HashMap是對K和V兩個Heap Object的強引用。HashMap是通過remove(Object key)方法將key對應的Entry(Heap Object)所指向的引用,指向next Entry.從而使key對應的Entry失去引用,進而使其在GC觸發時得到回收。

而WeakHashMap的put方法:

@Override
    public V put(K key, V value) {
        poll();
        int index = 0;
        Entry<K, V> entry;
        if (key != null) {
            index = (Collections.secondaryHash(key) & 0x7FFFFFFF) % elementData.length;
            entry = elementData[index];//-----------------------------------------------WeakHashMap的資料依然存於陣列:Entry<K,V>[] elementData;
            while (entry != null && !key.equals(entry.get())) {
                entry = entry.next;
            }
        } else {
            entry = elementData[0];
            while (entry != null && !entry.isNull) {
                entry = entry.next;
            }
        }
        if (entry == null) {
            modCount++;
            if (++elementCount > threshold) {
                rehash();
                index = key == null ? 0 : (Collections.secondaryHash(key) & 0x7FFFFFFF)
                        % elementData.length;
            }
            entry = new Entry<K, V>(key, value, referenceQueue);//-------------------Heap Object(key和value)賦給了Entry物件的key和value屬性。
            entry.next = elementData[index];
            elementData[index] = entry;
            return null;
        }
        V result = entry.value;
        entry.value = value;
        return result;
    }
private static final class Entry<K, V> extends WeakReference<K> implements //-----------WeakHashMap中的Entry是繼承WeakReference.
            Map.Entry<K, V> {
        final int hash;

        boolean isNull;

        V value;

        Entry<K, V> next;
... ...

所以,WeakHaspMap是對K和V的弱引用。

ArrayList(add方法)新增物件的引用

@Override public boolean add(E object) {
        Object[] a = array;
        int s = size;
        if (s == a.length) {
            Object[] newArray = new Object[s +
                    (s < (MIN_CAPACITY_INCREMENT / 2) ?
                     MIN_CAPACITY_INCREMENT : s >> 1)];//---------------ArrayList也是將資料存於陣列。【LinkedList並不是存於陣列】
            System.arraycopy(a, 0, newArray, 0, s);//-------------------這個方法對陣列擴容,並保證新增順序不變。
            array = a = newArray;
        }
        a[s] = object;//------------------------------Heap Object賦給了陣列的最後一個位置。
        size = s + 1;
        modCount++;
        return true;
    }
@Override public void clear() {
        if (size != 0) {
            Arrays.fill(array, 0, size, null);//----------將null賦給array中從0到size位置的所有元素。
            size = 0;
            modCount++;
        }
    }
public static void fill(Object[] array, int start, int end, Object value) {
        Arrays.checkStartAndEnd(array.length, start, end);
        for (int i = start; i < end; i++) {
            array[i] = value;//------------------------------將start到end位置的array元素都賦值為null.
        }
    }
顯然,ArrayList是對object的強引用。而clear()方法,是將所有引用置空,這樣就是可以使E object在GC觸發時得到回收。

而List並沒有現成的WeakList。

LRU策略

LRU(Least Recently Used)是利用“強引用”LinkedHashMap(將accessOrder設為true)儲存資料。

通過LinkedHashMap的eldest()方法獲取到最老(使用次數最低)的Entry eldest,

然後通過LruCache類的put(K,V)方法將資料新增到LinkedHashMap map中,當map中所有V的大小總和大於LruCache所設定的最大值maxSize時,就呼叫map.remove(K)方法,使V(Heap Object)失去Entry的引用,以達到便於GC回收這部分記憶體的目的。

/**
     * True if access ordered, false if insertion ordered.
     */
    private final boolean accessOrder;//-------true,則按照訪問的順序排序。
public LinkedHashMap() {
        init();
        accessOrder = false;//------------------預設是false,按照插入的順序排序。
    }
public final V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("key == null || value == null");
        }

        V previous;
        synchronized (this) {
            putCount++;
            size += safeSizeOf(key, value);
            previous = map.put(key, value);
            if (previous != null) {
                size -= safeSizeOf(key, previous);
            }
        }

        if (previous != null) {
            entryRemoved(false, key, previous, value);
        }

        trimToSize(maxSize);//------------每次新增資料,都要判斷快取大小。
        return previous;
    }
public void trimToSize(int maxSize) {
        while (true) {
            K key;
            V value;
            synchronized (this) {
                if (size < 0 || (map.isEmpty() && size != 0)) {
                    throw new IllegalStateException(getClass().getName()
                            + ".sizeOf() is reporting inconsistent results!");
                }

                if (size <= maxSize) {
                    break;
                }

                Map.Entry<K, V> toEvict = map.eldest();
                if (toEvict == null) {
                    break;
                }

                key = toEvict.getKey();
                value = toEvict.getValue();
                map.remove(key);//---------------------當size>maxSize了,則map.remove();
                size -= safeSizeOf(key, value);
                evictionCount++;
            }

            entryRemoved(true, key, value, null);
        }
    }
@Override public V remove(Object key) {
        if (key == null) {
            return removeNullKey();
        }
        int hash = Collections.secondaryHash(key);
        HashMapEntry<K, V>[] tab = table;
        int index = hash & (tab.length - 1);
        for (HashMapEntry<K, V> e = tab[index], prev = null;
                e != null; prev = e, e = e.next) {
            if (e.hash == hash && key.equals(e.key)) {
                if (prev == null) {
                    tab[index] = e.next;
                } else {
                    prev.next = e.next;
                }
                modCount++;
                size--;
                postRemove(e);//-----------------------------HashMap的remove後,呼叫了空實現方法postRemove();
                return e.value;
            }
        }
        return null;
    }
@Override void postRemove(HashMapEntry<K, V> e) {//----------------LinkedHashMap方法覆寫了postRemove方法,
        LinkedEntry<K, V> le = (LinkedEntry<K, V>) e;
        le.prv.nxt = le.nxt;
        le.nxt.prv = le.prv;
        le.nxt = le.prv = null; // Help the GC (for performance)//-------------將Heap Object失去LinkedEntry le的引用,以便GC回收。
    }
Lru策略,最終只是將Heap Object失去引用,並未強制觸發GC,所以LruCache的maxSize的設定不應該接近android虛擬機器的最大Heap Size,否則同樣容易引起記憶體溢位!!!