從名字可以得知主要和Map有關(guān),不過(guò)還有一個(gè)Weak,我們就更能自然而然的想到這里面還牽扯到一種弱引用結(jié)構(gòu),因此想要徹底搞懂,我們還需要知道四種引用,需要的朋友可以參考下" />

欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java中WeakHashMap的工作原理詳解

 更新時(shí)間:2023年09月07日 09:07:19   作者:南淮北安  
這篇文章主要介紹了Java中WeakHashMap的工作原理詳解,WeakHashMap
從名字可以得知主要和Map有關(guān),不過(guò)還有一個(gè)Weak,我們就更能自然而然的想到這里面還牽扯到一種弱引用結(jié)構(gòu),因此想要徹底搞懂,我們還需要知道四種引用,需要的朋友可以參考下

一、什么是 WeakHashMap?

從名字可以得知主要和Map有關(guān),不過(guò)還有一個(gè)Weak,我們就更能自然而然的想到這里面還牽扯到一種弱引用結(jié)構(gòu),因此想要徹底搞懂,我們還需要知道四種引用。

強(qiáng)引用: 如果一個(gè)對(duì)象具有強(qiáng)引用,它就不會(huì)被垃圾回收器回收。即使當(dāng)前內(nèi)存空間不足,JVM也不會(huì)回收它,而是拋出 OutOfMemoryError 錯(cuò)誤,使程序異常終止。 比如 String str = "hello" 這時(shí)候str就是一個(gè)強(qiáng)引用。

軟引用: 內(nèi)存足夠的時(shí)候,軟引用對(duì)象不會(huì)被回收,只有在內(nèi)存不足時(shí),系統(tǒng)則會(huì)回收軟引用對(duì)象,如果回收了軟引用對(duì)象之后仍然沒(méi)有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常。

弱引用: 如果一個(gè)對(duì)象具有弱引用,在垃圾回收時(shí)候,一旦發(fā)現(xiàn)弱引用對(duì)象,無(wú)論當(dāng)前內(nèi)存空間是否充足,都會(huì)將弱引用回收。

虛引用: 如果一個(gè)對(duì)象具有虛引用,就相當(dāng)于沒(méi)有引用,在任何時(shí)候都有可能被回收。 使用虛引用的目的就是為了得知對(duì)象被GC的時(shí)機(jī),所以可以利用虛引用來(lái)進(jìn)行銷(xiāo)毀前的一些操作,比如說(shuō)資源釋放等。

WeakHashMap是基于弱引用的,也就是說(shuō)只要垃圾回收機(jī)制一開(kāi)啟,就直接開(kāi)始了掃蕩,看見(jiàn)了就清除

二、為什么需要 WeakHashMap?

WeakHashMap正是由于使用的是弱引用,因此它的對(duì)象可能被隨時(shí)回收。

更直觀的說(shuō),當(dāng)使用 WeakHashMap 時(shí),即使沒(méi)有刪除任何元素,它的尺寸、get方法也可能不一樣。

比如:

(1)調(diào)用兩次 size() 方法返回不同的值;第一次為10,第二次就為8了。

(2)兩次調(diào)用 isEmpty() 方法,第一次返回false,第二次返回true;

(3)兩次調(diào)用 containsKey() 方法,第一次返回true,第二次返回false;

(4)兩次調(diào)用 get() 方法,第一次返回一個(gè)value,第二次返回null;

是不是覺(jué)得有點(diǎn)惡心,這種飄忽不定的東西好像沒(méi)什么用,試想一下,你準(zhǔn)備使用WeakHashMap保存一些數(shù)據(jù),寫(xiě)著寫(xiě)著都沒(méi)了,那還保存?zhèn)€啥呀。

不過(guò)有一種場(chǎng)景,最喜歡這種飄忽不定、一言不合就刪除的東西。

那就是緩存,在緩存場(chǎng)景下,由于內(nèi)存是有限的,不能緩存所有對(duì)象,因此就需要一定的刪除機(jī)制,淘汰掉一些對(duì)象

現(xiàn)在我們已經(jīng)知道了WeakHashMap是基于弱引用,其對(duì)象可能隨時(shí)被回收,適用于緩存的場(chǎng)景。

三、WeakHashMap 的例子

public class TestWeakHashMap {
    public static void main(String[] args) {
        WeakHashMap<String, String> weakHashMap = new WeakHashMap<>(10);
        String key0 = new String("str1");
        String key1 = new String("str2");
        String key2 = new String("str3");
        // 存放元素
        weakHashMap.put(key0, "data1");
        weakHashMap.put(key1, "data2");
        weakHashMap.put(key2, "data3");
        System.out.printf("weakHashMap: %s\n", weakHashMap);
        // 是否包含某key
        System.out.printf("contains key str1 : %s\n", weakHashMap.containsKey(key0));
        System.out.printf("contains key str2 : %s\n", weakHashMap.containsKey(key1));
        // 移除key
        weakHashMap.remove(key0);
        System.out.printf("weakHashMap after remove: %s", weakHashMap);
        // 這意味著"弱鍵"key1再?zèng)]有被其它對(duì)象引用,調(diào)用gc時(shí)會(huì)回收WeakHashMap中與key1對(duì)應(yīng)的鍵值對(duì)
        key1 = null;
        // 內(nèi)存回收,這里會(huì)回收WeakHashMap中與"key0"對(duì)應(yīng)的鍵值對(duì)
        System.gc();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 遍歷WeakHashMap
        for (Map.Entry<String, String> m : weakHashMap.entrySet()) {
            System.out.printf("next : %s >>> %s\n", m.getKey(), m.getValue());
        }
        // 打印WeakHashMap的實(shí)際大小
        System.out.printf("after gc WeakHashMap size: %s\n", weakHashMap.size());
    }
}

在這里插入圖片描述

上面的例子展示了WeakHashMap的增刪改查,以及弱鍵的回收,可以看到把Key的引用置為null, gc后,會(huì)將該鍵值對(duì)回收。

四、WeakHashMap 的使用場(chǎng)景

一般用做緩存,比如Tomcat的源碼里,實(shí)現(xiàn)緩存時(shí)會(huì)用到WeakHashMap, 在緩存系統(tǒng)中,使用WeakHashMap可以避免內(nèi)存泄漏,但是使用WeakHashMap做緩存時(shí)要注意,如果只有它的key只有WeakHashMap本身在用,而在WeakHashMap之外沒(méi)有對(duì)該key的強(qiáng)引用,那么GC時(shí)會(huì)回收這個(gè)key對(duì)應(yīng)的entry。

所以WeakHashMap不能用做主緩存,合適的用法應(yīng)該是用它做二級(jí)的內(nèi)存緩存,即那么過(guò)期緩存數(shù)據(jù)或者低頻緩存數(shù)據(jù)

public final class ConcurrentCache<K,V> {
    private final int size;
    private final Map<K,V> eden;
    private final Map<K,V> longterm;
    public ConcurrentCache(int size) {
        this.size = size;
        this.eden = new ConcurrentHashMap<>(size);
        this.longterm = new WeakHashMap<>(size);
    }
    public V get(K k) {
        V v = this.eden.get(k);
        if (v == null) {
            synchronized (longterm) {
                v = this.longterm.get(k);
            }
            if (v != null) {
                this.eden.put(k, v);
            }
        }
        return v;
    }
    public void put(K k, V v) {
        if (this.eden.size() >= size) {
            synchronized (longterm) {
                this.longterm.putAll(this.eden);
            }
            this.eden.clear();
        }
        this.eden.put(k, v);
    }
}

源碼中有eden和longterm的兩個(gè)map,對(duì)jvm堆區(qū)有所了解的話,可以猜測(cè)出tomcat在這里是使用ConcurrentHashMap和WeakHashMap做了分代的緩存。

在put方法里,在插入一個(gè)k-v時(shí),先檢查eden緩存的容量是不是超了。

沒(méi)有超就直接放入eden緩存,如果超了則鎖定longterm將eden中所有的k-v都放入longterm。

再將eden清空并插入k-v。

在get方法中,也是優(yōu)先從eden中找對(duì)應(yīng)的v,如果沒(méi)有則進(jìn)入longterm緩存中查找,找到后就加入eden緩存并返回。

經(jīng)過(guò)這樣的設(shè)計(jì),相對(duì)常用的對(duì)象都能在eden緩存中找到,不常用(有可能被銷(xiāo)毀的對(duì)象)的則進(jìn)入longterm緩存。

而longterm的key的實(shí)際對(duì)象沒(méi)有其他引用指向它時(shí),gc就會(huì)自動(dòng)回收heap中該弱引用指向的實(shí)際對(duì)象,弱引用進(jìn)入引用隊(duì)列。

longterm調(diào)用expungeStaleEntries()方法,遍歷引用隊(duì)列中的弱引用,并清除對(duì)應(yīng)的Entry,不會(huì)造成內(nèi)存空間的浪費(fèi)。

五、WeakHashMap 的數(shù)據(jù)結(jié)構(gòu)

1. 類(lèi)的定義

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

2. 常量與變量

    private static final int DEFAULT_INITIAL_CAPACITY = 16;
    private static final int MAXIMUM_CAPACITY = 1 << 30;
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;
    Entry<K,V>[] table;
    private int size;
    private int threshold;
    private final float loadFactor;
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
    int modCount;
  • DEFAULT_INITIAL_CAPACITY : 初始容量
  • MAXIMUM_CAPACITY : 最大容量
  • DEFAULT_LOAD_FACTOR : 默認(rèn)加載因子
  • table : Entry數(shù)組
  • size : 實(shí)際存放的數(shù)據(jù)個(gè)數(shù)
  • threshold : 擴(kuò)容閾值
  • loadFactor : 加載因子
  • queue : 引用隊(duì)列
  • modCount : 修改次數(shù)

3. Entry 類(lèi)

   private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        final int hash;
        Entry<K,V> next;
        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 V getValue() {
            return value;
        }
        public V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
        ...
    }

可以看到 Entry 類(lèi)實(shí)現(xiàn)了 Map.Entry 接口,繼承弱引用 (WeakReference),屬性有 key,value 和 next 引用。

構(gòu)造器中需要傳入一個(gè)引用隊(duì)列,方法主要看getKey():

return (K) WeakHashMap.unmaskNull(get());

再來(lái)看看 WeakHashMap 的靜態(tài)方法 unmaskNull():

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

判斷key是否等于NULL_KEY來(lái)選擇是否返回null。

4. 類(lèi)關(guān)系圖

在這里插入圖片描述

六、WeakHashMap 的弱鍵回收

上面看完 WeakHashMap 的數(shù)據(jù)結(jié)構(gòu),那么 WeakHashMap 是如何實(shí)現(xiàn)弱鍵回收的呢?

其實(shí)根據(jù)前面的文章也能猜到,利用Reference和ReferenceQueue。

1. put 數(shù)據(jù)

    public V put(K key, V value) {
        Object k = maskNull(key);
        int h = hash(k);
        Entry<K,V>[] 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, value, queue, h, e);
        if (++size >= threshold)
            resize(tab.length * 2);
        return null;
    }
   private static Object maskNull(Object key) {
        return (key == null) ? NULL_KEY : key;
    }

判斷key是否為null,為null的話將key賦值為NULL_KEY。 計(jì)算key的hash值,然后根據(jù)hash值查找待插入的位置。

遍歷Entry數(shù)組,看該鍵是否已存在,存在的話則替換舊值,并返回舊值。 不存在則構(gòu)建Entry對(duì)象存入數(shù)組。

這個(gè)流程和HashMap,HashTable等差不多。

2. get 數(shù)據(jù)

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

一句話,先根據(jù)key的hash值找到索引位置,然后拿到Entry對(duì)象,再判斷該Entry是否存在下一個(gè)引用(即hash碰撞),遍歷該單鏈表,比較value值。

3. 弱鍵如何回收?

根據(jù)上面的分析就很容易得出了,WeakHashMap內(nèi)部的數(shù)據(jù)存儲(chǔ)是用Entry[]數(shù)組,即鍵值對(duì)數(shù)組。

Entry類(lèi)繼承于WeakReferece(弱引用),

弱引用的特點(diǎn)再重申下:當(dāng) JVM 進(jìn)行垃圾回收時(shí),無(wú)論內(nèi)存是否充足,都會(huì)回收被弱引用關(guān)聯(lián)的對(duì)象。

在構(gòu)造一個(gè)Entry對(duì)象的時(shí)候,會(huì)傳入一個(gè)ReferenceQueue,key為弱引用包裹的對(duì)象:

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

再來(lái)看看WeakHashMap如何清理已經(jīng)被回收的key的,被回收的key會(huì)存放在引用隊(duì)列中:

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

遍歷引用隊(duì)列,然后刪除已被回收的鍵值對(duì)(從數(shù)組移除,改變單鏈表結(jié)點(diǎn)引用,將value賦值為null),該方法會(huì)在WeakHashMap增刪改查、擴(kuò)容的地方調(diào)用。

因?yàn)橐粋€(gè)key-value存放到WeakHashMap中后,key會(huì)被用弱引用包起來(lái)存儲(chǔ),如果這個(gè)key在WeakHashMap外部沒(méi)有強(qiáng)引用的話,GC時(shí)將被回收,然后WeakHashMap根據(jù)引用隊(duì)列對(duì)已回收的key做清理。

到此這篇關(guān)于Java中WeakHashMap的工作原理詳解的文章就介紹到這了,更多相關(guān)WeakHashMap工作原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java使用Redis實(shí)現(xiàn)微博熱搜功能

    Java使用Redis實(shí)現(xiàn)微博熱搜功能

    在社交平臺(tái)上,熱搜功能是一個(gè)非常重要的組成部分,它展示了當(dāng)前最熱門(mén)的話題,幫助用戶迅速了解最受關(guān)注的事件,Redis 是一個(gè)高性能的鍵值存儲(chǔ)系統(tǒng),通常用于緩存和實(shí)時(shí)數(shù)據(jù)存儲(chǔ),本文將通過(guò) Java 結(jié)合 Redis 實(shí)現(xiàn)一個(gè)簡(jiǎn)化版的微博熱搜功能,需要的朋友可以參考下
    2024-12-12
  • springmvc+kindeditor文件上傳實(shí)例詳解

    springmvc+kindeditor文件上傳實(shí)例詳解

    這篇文章主要為大家詳細(xì)介紹了springmvc+kindeditor文件上傳實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-08-08
  • 深入探究 spring-boot-starter-parent的作用

    深入探究 spring-boot-starter-parent的作用

    這篇文章主要介紹了spring-boot-starter-parent的作用詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,感興趣的小伙伴可以跟著小編一起來(lái)學(xué)習(xí)一下
    2023-05-05
  • 使用Java?Executors創(chuàng)建線程池的9種方法

    使用Java?Executors創(chuàng)建線程池的9種方法

    文章主要介紹了?Java?中Executors類(lèi)創(chuàng)建線程池的?9?種方法,每種方法都詳細(xì)闡述了實(shí)現(xiàn)原理、源代碼分析、參數(shù)解釋、實(shí)現(xiàn)過(guò)程、特性和使用場(chǎng)景,感興趣的小伙伴跟著小編一起來(lái)看看吧
    2024-11-11
  • JavaFX實(shí)現(xiàn)界面跳轉(zhuǎn)

    JavaFX實(shí)現(xiàn)界面跳轉(zhuǎn)

    這篇文章主要為大家詳細(xì)介紹了JavaFX實(shí)現(xiàn)界面跳轉(zhuǎn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • jdk-logging?log4j?logback日志系統(tǒng)實(shí)現(xiàn)機(jī)制原理介紹

    jdk-logging?log4j?logback日志系統(tǒng)實(shí)現(xiàn)機(jī)制原理介紹

    這篇文章主要介紹了jdk-logging、log4j、logback日志介紹以及三個(gè)日志系統(tǒng)的實(shí)現(xiàn)機(jī)制,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-03-03
  • java的io操作(將字符串寫(xiě)入到txt文件中)

    java的io操作(將字符串寫(xiě)入到txt文件中)

    這篇文章主要介紹了java的io操作示例,將字符串寫(xiě)入到txt文件中,需要的朋友可以參考下
    2014-04-04
  • 微信隨機(jī)生成紅包金額算法java版

    微信隨機(jī)生成紅包金額算法java版

    這篇文章主要為大家詳細(xì)介紹了java和php版的微信隨機(jī)生成紅包金額算法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-07-07
  • 淺談一段java代碼是如何執(zhí)行的

    淺談一段java代碼是如何執(zhí)行的

    這篇文章主要介紹了淺談一段java代碼是如何執(zhí)行的,小編覺(jué)得不錯(cuò),下面就一起來(lái)了解一下
    2021-04-04
  • Mac下設(shè)置Java默認(rèn)版本的方法

    Mac下設(shè)置Java默認(rèn)版本的方法

    今天工作的時(shí)候發(fā)現(xiàn)了一個(gè)錯(cuò)誤,提示java版本太低,無(wú)法啟動(dòng)!想起自己裝過(guò)高版本的Java,但是卻沒(méi)有默認(rèn)啟動(dòng),從網(wǎng)上找了一些資料,整理下現(xiàn)在分享給大家,有需要的可以參考借鑒。
    2016-10-10

最新評(píng)論