WeakHashMap用法以及原理以及Reference原理

307 阅读4分钟

概述

WeakHashMap 常用于缓存,当发生GC的时候,整个Map会被清空。

public static void main(String[] args) throws InterruptedException {
    String key1 = new String("k1");
    String key2 = new String("K2");
    Map<String, String> wmap = new WeakHashMap<String, String>();
    wmap.put(key1, "wval1");
    wmap.put(key2, "wval2");
    key2 = null;
    key1 = null;
    System.gc();
    System.out.println("first  access: " + wmap.get("k1"));
    System.out.println("second access: " + wmap.get("k2"));
}
first  access: null
second access: null

我们来看看WeakHashMap的源码

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

    Entry<K,V>[] table;

    # 容器中存放的Node继承与弱引用
    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) {
            # key 是弱引用
            super(key, queue);
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }
    }
}

从源码中可以发现WeakHashMap里面存放的Entry是继承与 WeakReference,所以我们得先看看WeakReference的用法。

WeakReference 和 ReferenceQueue

首先先说结论,然后再来推导。 如果 WeakReference 注册到了 ReferenceQueue 中,当WeakReference里面引用的对象被GC了,这个Queue会收到通知。

public static  class WeakPerson extends WeakReference<Object>{

    private String name;

    @Override
    protected void finalize() throws Throwable {
        System.out.println("weakkperson 结束");
    }

    public WeakPerson(Object referent, ReferenceQueue<Object> q, String name) {
        super(referent, q);
        this.name = name;
    }
}
public static void main(String[] args) throws Exception{
    String wife = new String("jerry");
    ReferenceQueue<Object> queue = new ReferenceQueue<>();
    WeakPerson peter = new WeakPerson(wife, queue, "peter");
    new Thread(()->{
        while (true){
            WeakPerson weakPerson = (WeakPerson)queue.poll();
            if (weakPerson == null){
                continue;
            }
            System.out.println(weakPerson.name+"---容器还存在");
        }
    }).start();
    wife = null;
    System.out.println("gc拉");
    System.gc();
    System.in.read();
}

image.png

Queue是如何能找到对象被销毁的

JVM在GC时如果当前对象只被Reference对象引用,JVM会根据Reference具体类型与堆内存的使用情况决定是否把对应的Reference对象加入到一个由Reference构成的pending链表上,如果能加入pending链表JVM同时会通知ReferenceHandler线程进行处理。该线程就会把Reference放到队列中,Queue就找到对象了

image.png

Reference 的四个状态

  1. Active 新建状态,刚刚new出来的 Reference就是这个状态
  2. Pending 如果Reference 引用的数据被销毁了,并且自身还注册到了一个Queue中,就会变成 Pending状态,准备入自己注册的Queue
  3. Enqueued 已经在自己的队列中了

image.png

源码阅读

Reference 里面很多变量都跟状态有关,状态不同,指向的位置不同,都是由JVM来控制的

public abstract class Reference<T> {

    # Reference 引用的对象
    private T referent;         /* Treated specially by GC */
    # 注册的队列
    volatile ReferenceQueue<? super T> queue;

    /* When active:   NULL
     *     pending:   this
     *    Enqueued:   next reference in queue (or this if last)
     *    Inactive:   this
     */
    # next指针,根据状态不同指向不同
    volatile Reference next;

    /* When active:   next element in a discovered reference list maintained by GC (or this if last)
     *     pending:   next element in the pending list (or null if last)
     *   otherwise:   NULL
     */
    transient private Reference<T> discovered;  /* used by VM */


    /* List of References waiting to be enqueued.  The collector adds
     * References to this list, while the Reference-handler thread removes
     * them.  This list is protected by the above lock object. The
     * list uses the discovered field to link its elements.
     */
    private static Reference<Object> pending = null;

    # 这个线程会在 Reference 是pending的时候被调用
    private static class ReferenceHandler extends Thread {
    
        public void run() {
            while (true) {
                tryHandlePending(true);
            }
        }
    }
    # 让pending的 Reference放到注册的队列中
    static boolean tryHandlePending(boolean waitForNotify) {
        Reference<Object> r;
        Cleaner c;
        try {
            synchronized (lock) {
                if (pending != null) {
                    r = pending;
                    // 'instanceof' might throw OutOfMemoryError sometimes
                    // so do this before un-linking 'r' from the 'pending' chain...
                    c = r instanceof Cleaner ? (Cleaner) r : null;
                    // unlink 'r' from 'pending' chain
                    pending = r.discovered;
                    r.discovered = null;
                } else {
                    // The waiting on the lock may cause an OutOfMemoryError
                    // because it may try to allocate exception objects.
                    if (waitForNotify) {
                        lock.wait();
                    }
                    // retry if waited
                    return waitForNotify;
                }
            }
        } catch (OutOfMemoryError x) {
            // Give other threads CPU time so they hopefully drop some live references
            // and GC reclaims some space.
            // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
            // persistently throws OOME for some time...
            Thread.yield();
            // retry
            return true;
        } catch (InterruptedException x) {
            // retry
            return true;
        }

        // Fast path for cleaners
        if (c != null) {
            c.clean();
            return true;
        }

        # 这就是入队列的操作
        ReferenceQueue<? super Object> q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        return true;
    }

    static {
       # 开启线程去监听有没有 Reference 变成 pending
        ThreadGroup tg = Thread.currentThread().getThreadGroup();
        for (ThreadGroup tgn = tg;
             tgn != null;
             tg = tgn, tgn = tg.getParent());
        Thread handler = new ReferenceHandler(tg, "Reference Handler");
        /* If there were a special system-only priority greater than
         * MAX_PRIORITY, it would be used here
         */
        handler.setPriority(Thread.MAX_PRIORITY);
        handler.setDaemon(true);
        handler.start();

        // provide access in SharedSecrets
        SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
            @Override
            public boolean tryHandlePendingReference() {
                return tryHandlePending(false);
            }
        });
    }
}

ReferenceQueue 源码阅读。没什么特别的,就是一个单项链表,然后取的时候加了个锁

public class ReferenceQueue<T> {



    static ReferenceQueue<Object> NULL = new Null<>();
    static ReferenceQueue<Object> ENQUEUED = new Null<>();

    static private class Lock { };
    private Lock lock = new Lock();
    private volatile Reference<? extends T> head = null;
    private long queueLength = 0;

    # 入队列的操作
    boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
        synchronized (lock) {
            // Check that since getting the lock this reference hasn't already been
            // enqueued (and even then removed)
            ReferenceQueue<?> queue = r.queue;
            if ((queue == NULL) || (queue == ENQUEUED)) {
                return false;
            }
            assert queue == this;
            r.queue = ENQUEUED;
            r.next = (head == null) ? r : head;
            head = r;
            queueLength++;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(1);
            }
            lock.notifyAll();
            return true;
        }
    }
    # 就是一个简单的从队列中找数据如果没有就返回null的操作
    private Reference<? extends T> reallyPoll() {       /* Must hold lock */
        Reference<? extends T> r = head;
        if (r != null) {
            @SuppressWarnings("unchecked")
            Reference<? extends T> rn = r.next;
            head = (rn == r) ? null : rn;
            r.queue = NULL;
            r.next = r;
            queueLength--;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(-1);
            }
            return r;
        }
        return null;
    }


    public Reference<? extends T> poll() {
        if (head == null)
            return null;
        synchronized (lock) {
            return reallyPoll();
        }
    }
}

回到WeakHashMap

通过了学习 WeakReference 的相关知识,发现 WeakHashMap 里面的 Node的key如果销毁了会触发一个Queue的监听事件,我们来看看源码. 一次 get请求会把所有的已经销毁的key对应的value也置空.等下次gc对应的value就被清理了

# 获得table之前都会调用 expungeStaleEntries 方法
private Entry<K,V>[] getTable() {
    expungeStaleEntries();
    return table;
}

private void expungeStaleEntries() {
    # 监听 queue,一次调用会把 queue中所有的数据清除.
    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;
            }
        }
    }
}