最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

WeakHashMap的垃圾回收原理詳解

 更新時間:2023年09月08日 09:16:52   作者:半夏_2021  
這篇文章主要介紹了WeakHashMap的垃圾回收原理詳解,WeakHashMap 與 HashMap 的用法基本類似,與 HashMap 的區(qū)別在于,HashMap的key保留了對實際對象的強引用個,這意味著只要該HashMap對象不被銷毀,該HashMap的所有key所引用的對象就不會被垃圾回收,需要的朋友可以參考下

WeakHashMap 介紹

WeakHashMap 與 HashMap 的用法基本類似。與 HashMap 的區(qū)別在于,HashMap 的key 保留了對實際對象的強引用個,這意味著只要該HashMap對象不被銷毀,該HashMap的所有key所引用的對象就不會被垃圾回收,HashMap也不會自動刪除這些key所對應的key-value 對;

但WeakHashMap的key 只保留了對實際對象的弱引用,這意味著如果WeakHashMap對象的key所引用的對象沒有被其他強引用變量所引用個,則這些key所引用的對象可能被垃圾回收,WeakHashMap也可能自動刪除這些key所對應的key-value對。

WeakHashMap的數據結構

類的定義

在這里插入圖片描述

public class WeakHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V> {
}

WeakHashMap 因為GC的時候會把沒有強引用的key回收掉,所以它里面的元素不會太多。

因此,WeakHashMap 的存儲結構只有 數組 + 鏈表

變量和常量

  // 默認初始容量為16
   private static final int DEFAULT_INITIAL_CAPACITY = 16;
   // 最大容量為2的30次方
    private static final int MAXIMUM_CAPACITY = 1 << 30;
   //默認裝載因子
   private static final float DEFAULT_LOAD_FACTOR = 0.75f;
   // 桶
    Entry<K,V>[] table;
    //元素個數
    private int size;
    // 擴容門檻,等于capacity * loadFactor
    private int threshold;
   // 裝載因子
    private final float loadFactor;
    /**
     * 引用隊列,當弱鍵失效的時候會把Entry添加到這個隊列中
     */
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
     // 修改次數
    int modCount;

Entry 內部類

Entry 內部類并沒有key屬性,因為key屬性存儲在 Reference 類中

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) {
            // 調用Reference的構造方法初始化key和引用隊列
            super(key, queue);
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }
}
public class WeakReference<T> extends Reference<T> {
   // 調用Reference的構造方法初始化 key 和 引用隊列
    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}
public abstract class Reference<T> {
    // 實際存儲key的地方
    private T referent;      
    // 引用隊列
    volatile ReferenceQueue<? super T> queue;
    Reference(T referent) {
        this(referent, null);
    }
    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }
}

從Entry的構造方法我們知道,key和queue最終會傳到到Reference的構造方法中,這里的key就是Reference的referent屬性,它會被gc特殊對待,即當沒有強引用存在時,當下一次gc的時候會被清除。

構造方法

public WeakHashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Initial Capacity: "+
                initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal Load factor: "+
                loadFactor);
    int capacity = 1;
    while (capacity < initialCapacity)
        capacity <<= 1;
    table = newTable(capacity);
    this.loadFactor = loadFactor;
    threshold = (int)(capacity * loadFactor);
}
public WeakHashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public WeakHashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
public WeakHashMap(Map<? extends K, ? extends V> m) {
    this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
            DEFAULT_INITIAL_CAPACITY),
            DEFAULT_LOAD_FACTOR);
    putAll(m);
}

構造方法和 HashMap基本類似,初始容量為大于等于傳入容量的2的n次方,擴容的門檻 threshold 等于 capacity * loadFactor 。

put(K key, V value)

    public V put(K key, V value) {
        // 如果key為空,用空對象代替
        Object k = maskNull(key);
        // 計算key的hash值
        int h = hash(k);
        // 獲取桶
        Entry<K,V>[] tab = getTable();
        // 計算元素在哪個桶中,h & (length-1)
        int i = indexFor(h, tab.length);
      // 遍歷桶對應的鏈表
        for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
            if (h == e.hash && eq(k, e.get())) {
               // 如果找到了元素就使用新值替換舊值,并返回舊值
                V oldValue = e.value;
                if (value != oldValue)
                    e.value = value;
                return oldValue;
            }
        }
        modCount++;
        // 如果沒找到就把新值插入到鏈表的頭部
        Entry<K,V> e = tab[i];
        tab[i] = new Entry<>(k, value, queue, h, e);
        // 如果插入元素后數量達到了擴容門檻就把桶的數量擴容為2倍大小
        if (++size >= threshold)
            resize(tab.length * 2);
        return null;
    }

(1)計算hash;

與HashMap不同的是 ,key為null 時,返回的hash時0

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

而 WeakHashMap 用空對象來計算

    private static final Object NULL_KEY = new Object();
    private static Object maskNull(Object key) {
        return (key == null) ? NULL_KEY : key;
    }

另外,HashMap 計算hash 只用了依次異或,而這里使用了四次

    final int hash(Object k) {
        int h = k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

(2) 計算再哪個桶

(3) 遍歷桶對應的鏈表

(4) 如果能找到元素,則用新值代替舊值

(5) 如果沒有找到就在鏈表的頭部插入新元素

(6) 如果元素數量達到了擴容門檻,就把容量擴大到原來容量的2倍;

resize(int newCapacity)

    void resize(int newCapacity) {
     // 獲取舊桶,getTable()的時候會剔除失效的Entry
        Entry<K,V>[] oldTable = getTable();
         // 舊容量
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
       // 新桶
        Entry<K,V>[] newTable = newTable(newCapacity);
        // 把元素從舊桶轉移到新桶
        transfer(oldTable, newTable);
        table = newTable;
     // 如果元素個數大于擴容門檻的一半,則使用新桶和新容量,并計算新的擴容門檻
        if (size >= threshold / 2) {
            threshold = (int)(newCapacity * loadFactor);
        } else {
          // 否則把元素再轉移回舊桶,還是使用舊桶
        // 因為在transfer的時候會清除失效的Entry,所以元素個數可能沒有那么大了,就不需要擴容了
            expungeStaleEntries();
            transfer(newTable, oldTable);
            table = oldTable;
        }
    }
    private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
    // 遍歷舊桶
        for (int j = 0; j < src.length; ++j) {
            Entry<K,V> e = src[j];
            src[j] = null;
            while (e != null) {
                Entry<K,V> next = e.next;
                Object key = e.get();
                // 如果key等于了null就清除,說明key被gc清理掉了,則把整個Entry清除
                if (key == null) {
                    e.next = null;  // Help GC
                    e.value = null; //  "   "
                    size--;
                } else {
                 // 否則就計算在新桶中的位置并把這個元素放在新桶對應鏈表的頭部
                    int i = indexFor(e.hash, dest.length);
                    e.next = dest[i];
                    dest[i] = e;
                }
                e = next;
            }
        }
    }

(1)判斷舊容量是否達到最大容量;

(2)新建新桶并把元素全部轉移到新桶中;

(3)如果轉移后元素個數不到擴容門檻的一半,則把元素再轉移回舊桶,繼續(xù)使用舊桶,說明不需要擴 容;

(4)否則使用新桶,并計算新的擴容門檻;

(5)轉移元素的過程中會把key為null的元素清除掉,所以size會變?。?/p>

垃圾回收原理

WeakHashMap  通過將一些沒有被引用的鍵的值賦值為null, 這樣就會告訴GC去回收這些存儲的值。

我們看下面例子:

public class WeakHashMapTest {
    public static void main(String[] args) {
        House seller1 = new House("1號賣家房源.");
        SellerInfo sellerInfo1 = new SellerInfo();
        House seller2 = new House("2號賣家房源");
        SellerInfo sellerInfo2 = new SellerInfo();
        WeakHashMap<House,SellerInfo> weakHashMap = new WeakHashMap<>();
        //如果換成 HashMap ,則Key是對House對象的強引用
        weakHashMap.put(seller1,sellerInfo1);
        weakHashMap.put(seller2,sellerInfo2);
        System.out.println("weakHashMap before null,size="+weakHashMap.size());
        seller1 = null;
        System.gc();
        System.runFinalization();
        //如果換成 HashMap ,size 依然等于2
        System.out.println("weakHashMap after null, size = "+weakHashMap.size());
        System.out.println(weakHashMap);
    }
}
class SellerInfo{}

最終的結果是size = 1,為什么為1呢? 因為 seller1 為null, 從而引起GC,那么 為什么我們把 null 作為鍵存進去,為什么不會導致被回收呢?

那么我們看 put 方法的源碼:

public V put(K key, V value) {
        K k = (K) maskNull(key);// 重點看這里
        int h = HashMap.hash(k.hashCode());
        Entry[] tab = getTable();
        int i = indexFor(h, tab.length);
        for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
            if (h == e.hash && eq(k, e.get())) {
                V oldValue = e.value;
                if (value != oldValue)
                    e.value = value;
                return oldValue;
            }
        }
        modCount++;
    Entry<K,V> e = tab[i];
        tab[i] = new Entry<K,V>(k, value, queue, h, e);
        if (++size >= threshold)
            resize(tab.length * 2);
        return null;
    }

我們重點看一些 這行代碼 K k = (K) maskNull(key);

    private static final Object NULL_KEY = new Object();
    private static Object maskNull(Object key) {
        return (key == null) ? NULL_KEY : key;
    }

如果key為null的話,返回 的是NULL_KEY 這個靜態(tài)值,這個靜態(tài)值就是 Object ,所以WeakHashMap 在存儲null為鍵的時候,其實存儲的是其本身的靜態(tài)成員變量 Object,也就是存儲不是null。

那WeakHashMap 是如何跟WeakReference 關聯起來的呢?

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

WeakHashMap的Entry是繼承WeakReference,這樣一來,整個Entry就是一個WeakReference,再來看看Entry的構造方法,調用了super(key, queue),也就是調用了這個構造方法

public class WeakReference<T> extends Reference<T> {
    public WeakReference(T referent) {
        super(referent);
    }
    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

有兩個參數,一個key,一個是queue, 這個key就是WeakHashMap 中存儲的key的值,這個queue 是WeakHashMap 中創(chuàng)建的 ReferenceQueue 。 那么 ReferenceQueue 有什么用呢?

當GC某個對象時,如果有此對象上還有弱引用與其關聯,會將WeakReference對象與Reference 類的pending 引用關聯起來,然后由 Reference Handler線程將該插入ReferenceQueue隊列。

也就是說Entry中的key被GC時,會你那個Entry 放入到 ReferenceQueue中,WeakHashMap就能通過ReferenceQueue中的Entry了解到哪些key已經被GC,或者即將馬上被GC,起到了通知的作用。

那么什么時候來判斷要講沒有被引用的key標記為null的呢?

在WeakHashMap的put(),get(),remove()等等方法中都調用了一個getTable()方法,而這個getTable()方法的源碼如下:

    private Entry<K,V>[] getTable() {
        expungeStaleEntries();
        return table;
    }

其實都是調用 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;
                }
            }
        }
    }

上面代碼中的queue 就是定義的成員變量

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

可以看到每調用一次expungeStaleEntries()方法,就會在引用隊列中尋找是否有將要被清除的key對象,如果有則在table中找到其值,并將value設置為null,next指針也設置為null,讓GC去回收這些資源。

總結

(1)WeakHashMap使用(數組 + 鏈表)存儲結構;

(2)WeakHashMap中的key是弱引用,gc的時候會被清除;

(3)每次對map的操作都會剔除失效key對應的Entry;

(4)使用String作為key時,一定要使用new String()這樣的方式聲明key,才會失效,其它的基本類型的包裝類型是一樣的;

(5)WeakHashMap常用來作為緩存使用;

到此這篇關于WeakHashMap的垃圾回收原理詳解的文章就介紹到這了,更多相關WeakHashMap垃圾回收內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Spring高級之注解@PropertySource的原理

    Spring高級之注解@PropertySource的原理

    這篇文章主要介紹了Spring高級之注解@PropertySource的原理,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • 用html css javascript打造自己的RIA圖文教程

    用html css javascript打造自己的RIA圖文教程

    用html&css&javascript打造自己的RIA之一,包括了配置等
    2009-07-07
  • SpringBoot中的配置類(@Configuration)

    SpringBoot中的配置類(@Configuration)

    這篇文章主要介紹了SpringBoot中的配置類(@Configuration),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • Java中常見的幾種四舍五入方法總結

    Java中常見的幾種四舍五入方法總結

    在Java編程中四舍五入是一個常見的數學運算需求,下面這篇文章主要給大家介紹了關于Java中常見的幾種四舍五入方法,文章通過代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2024-08-08
  • 圖解Java中歸并排序算法的原理與實現

    圖解Java中歸并排序算法的原理與實現

    歸并排序是建立在歸并操作上的一種有效的排序算法。該算法是采用分治法(Divide and Conquer)的一個非常典型的應用。本文將通過圖片詳解插入排序的原理及實現,需要的可以參考一下
    2022-08-08
  • SpringBoot自定義錯誤處理邏輯詳解

    SpringBoot自定義錯誤處理邏輯詳解

    這篇文章主要介紹了SpringBoot自定義錯誤處理邏輯,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2022-10-10
  • 關于Java整合RocketMQ實現生產消費詳解

    關于Java整合RocketMQ實現生產消費詳解

    這篇文章主要介紹了關于Java整合RocketMQ實現生產消費詳解,RocketMQ作為一款純java、分布式、隊列模型的開源消息中間件,支持事務消息、順序消息、批量消息、定時消息、消息回溯等,需要的朋友可以參考下
    2023-05-05
  • Mybatis insert方法主鍵回填和自定義操作

    Mybatis insert方法主鍵回填和自定義操作

    這篇文章主要介紹了Mybatis insert方法主鍵回填和自定義操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • 使用Java實現將HTML內容添加到PowerPoint中

    使用Java實現將HTML內容添加到PowerPoint中

    在Java應用程序中處理PowerPoint文檔常常面臨挑戰(zhàn),尤其是需要動態(tài)生成或更新內容時,本文將深入探討如何利用 Spire.Presentation for Java 庫,將HTML字符串中的文本和圖片無縫集成到PowerPoint幻燈片中,感興趣的小伙伴可以了解下
    2025-11-11
  • MyBatis-Plus自定義SQL的詳細過程記錄

    MyBatis-Plus自定義SQL的詳細過程記錄

    Java開發(fā)使用mybatis-plus來執(zhí)行sql操作,往往比mybatis能夠省時省力,下面這篇文章主要給大家介紹了關于MyBatis-Plus自定義SQL的相關資料,需要的朋友可以參考下
    2022-02-02

最新評論

深圳市| 贡觉县| 岗巴县| 霍林郭勒市| 芜湖市| 永丰县| 井陉县| 洮南市| 贺兰县| 南安市| 桦川县| 乌鲁木齐县| 青神县| 浦江县| 深水埗区| 鄂托克前旗| 贞丰县| 栖霞市| 太原市| 深水埗区| 东乡族自治县| 襄垣县| 花莲市| 柳河县| 云林县| 嘉兴市| 内乡县| 深州市| 晋中市| 福贡县| 安岳县| 曲靖市| 自贡市| 册亨县| 宿州市| 吉水县| 大荔县| 田阳县| 昌图县| 新平| 奉节县|