重识java-WeakHashMap

四种引用

  • 强引用(StrongReference)
    • 强引用是使用最广泛的引用,平时咱们常写的A a = new A();就是强引用
    • GC不会回收强引用,即便内存不足的状况下也不会,宁肯OutOfMemeryError
  • 软引用(SoftReference)
    • SoftReference的主要特色是具备较强的引用功能。
    • 只有当内存不够的时候才进行回收,而在内存足够的时候,一般不被回收。
    • 另外,引用对象还能保证在 Java 抛出 OutOfMemoryError 以前,被设置为null。
    • 软引用的使用能够参考guava-cache
  • 弱引用(WeakReference)
    • WeakReference 在垃圾回收器运行时,必定会被回收,而不像 SoftReference 须要条件。可是,若对象的引用关系复杂,则可能须要屡次回收才能达到目的。
  • 虚引用(PhantomReference)
    • PhantomReference主 要 用 于 辅 助finalize 方法。
    • PhantomReference 对象执行完了 finalize 方法后,成为 Unreachable Objects。但还未被回收,在此时,能够辅助 finalize 进行一些后期的回收工做。

弱引用的实现

put函数数据结构

public V put(K key, V value) {
	//若是key是null,则使用定义的常量NULL_KEY代替null。
    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) {
	    //若是原来有这个key,就替换并返回旧value
        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;
}

WeakHashMapEntry实现,继承了WeakReference,每个Entry都有其属于的ReferenceQueue,使得后面JVM在垃圾回收后,查找Reference对象组件pending链,并将Entry移动到ReferenceQueue成为可能。函数

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

    public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;
        K k1 = getKey();
        Object k2 = e.getKey();
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
            V v1 = getValue();
            Object v2 = e.getValue();
            if (v1 == v2 || (v1 != null && v1.equals(v2)))
                return true;
        }
        return false;
    }

    public int hashCode() {
        K k = getKey();
        V v = getValue();
        return Objects.hashCode(k) ^ Objects.hashCode(v);
    }

    public String toString() {
        return getKey() + "=" + getValue();
    }
}

Entry的构造函数中调用super(key, queue); 将Key处理成Reference:this

Reference(T referent, ReferenceQueue<? super T> queue) {
	this.referent = referent;
	this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

HashMap比较一下,Entry不直接引用Key这个对象,而是将引用关系放到了父类WeakReference中,能够看出WeakHashMap将传入的key包装成了WeakReference,并传入了一个ReferenceQueue;可是弱引用的实现细节仍是不清楚......线程

key如何清理

static private class Lock { };
private static Lock lock = new Lock();

//pending是一个链表结构
private static Reference<Object> pending = null;

static {
    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();
}

线程的优先级设成MAX,是一个什么样的线程须要如此高的权限?pending 、lock 都被static声明,lock.wait以后谁来唤醒,互联网上一顿搜罗,才明白JVM参与了这些事。 用通俗的话把JVM干的事串一下: 假设,WeakHashMap对象里面已经保存了不少对象的引用。JVM使用进行CMS GC垃圾回收的时候,会建立一个ConcurrentMarkSweepThread(简称CMST)线程去进行垃圾回收,ConcurrentMarkSweepThread线程被建立的同时会建立一个SurrogateLockerThread(简称SLT)线程而且启动它,SLT启动以后,处于等待阶段。 CMST开始垃圾回收时,会发一个消息给SLT让它去获取Java层Reference对象的全局锁:lock。 直到CMS GC完毕以后,JVM会将WeakHashMap中全部被回收的对象所属的WeakReference容器对象放入到Referencepending 属性当中(每次GC完毕以后,pending属性基本上都不会为null了),而后通知SLT释放而且notify全局锁: lock。此时激活了ReferenceHandler线程的run方法,使其脱离wait状态,开始工做了。ReferenceHandler这个线程会将pending中的全部WeakReference对象都移动到它们各自的列队当中,好比当前这个WeakReference属于某个WeakHashMap对象,那么它就会被放入相应的ReferenceQueue列队里面(该列队是链表结构)。code

Gc完成后, pending赋值,lock释放,此时ReferenceHandler 获取lock锁,将 pending 中的Reference对象压入了各自的 ReferenceQueue对象

pendingReference对象,给JVM使用的数据结构。pending.discovered返回下一个Reference对象。继承

// MAX_PRIORITY线程将pending的references所有入列
private static class ReferenceHandler extends Thread {

    ReferenceHandler(ThreadGroup g, String name) {
        super(g, name);
    }

    public void run() {
        for (;;) {
            Reference<Object> r;
            synchronized (lock) {
                if (pending != null) {
                    r = pending;
                    pending = r.discovered;
                    r.discovered = null;
                } else {
                    try {
                        try {
                            lock.wait();
                        } catch (OutOfMemoryError x) { }
                    } catch (InterruptedException x) { }
                    continue;
                }
            }

            // Fast path for cleaners
            if (r instanceof Cleaner) {
                ((Cleaner)r).clean();
                continue;
            }

            ReferenceQueue<Object> q = r.queue;
            if (q != ReferenceQueue.NULL) q.enqueue(r);
        }
    }
}

Entry清理

当GC以后,WeakHashMap对象里面get、put数据或者调用size方法的时候,WeakHashMapHashMap多了一个 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;
            }
        }
    }
}

expungeStaleEntries方法 就是将ReferenceQueue列队中的WeakReference依依poll出来去和Entry[]数据作比较,若是发现相同的,则说明这个Entry所保存的对象已经被GC掉了,那么将Entry[]内的Entry对象剔除掉,这样就把被GC掉的 WeakReference对应的EntryWeakHashMap中移除了。ci

Thanks for reading! want moreget

相关文章
相关标签/搜索