弱引用總結及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,否則同樣容易引起記憶體溢位!!!