新手读源码__java中的4种引用+WeakHashMap的弱引用的底层实现

前言

在《深入JVM》中提到过四种引用,但是对它们的认识却很少。如今又遇到了WeakHashMap,里面是弱引用,所以回过头来把4种引用的坑填上。通过本篇文章你可以了解:

  • 4种引用
  • 引用的几种状态
  • 如何实现弱引用的回收
  • WeakHashMap中弱引用回收机制

笔者源码来自

  • JAVA9

4种引用

引用 介绍
强引用 不会被GC的引用
弱引用(WeakReference) 弱引用在下一次GC时会被收集
软引用(SoftReference) 软引用在内存满的时候会被GC,也就是OOM的时候
虚引用(OhantomReference) 虚引用不会被get返回,GC不会自动清理虚引用,虚引用不会影响一个存在虚引用对象的GC回收

开场白,引用的四种状态

—————————–翻译源码的四种状态解释———————-

  • Active:新创建的实例对象处于active状态,当GC显示已经到了合适的状态之后,会使得实例的状态改变为Pending 状态,把实例对象加入Pending队列或者Inactive状态

  • Pending:pending- Reference list当中的一个元素,等待被Reference-handler 线程入队处理,没有注册过的实例不会进入这个状态

  • Enqueued: 队列中的一个元素,这个实例在被创建的时候被注册了。当实例从它的ReferenceQueue中移除的时候,会被设置成Inactive。没有被注册的实例不会到达这个状态

  • Inactive:最终状态不会再被改变

————————————四种状态源码的实现—————————-

  • Active 的时候,ReferenceQueue实例注册了,就是初始化的时候指定了一个引用队列。或者没有注册queue的时候ReferenceQueue.NULL。next是null。可以这么认为
    Reference(T referent) {
        this(referent, null);
    }

    Reference(T referent, ReferenceQueue<? super T> queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }
  • Pending 状态,queue必须注册,next=this

  • Enqueued状态,queue必须注册,next为队列中的下一个引用,如果是该队列中的最后一个就是this

  • Inactive状态,queue为ReferenceQueue.NULL

主要的流程

其主要的流程图如下。
这里写图片描述
或者可以这么将,上面的那条路线是程序员手动处理队列的方法,下面是交给GC智能处理的方法。这样理解就很容易了

WeakHashMap

WeakHashMap和HashMap差不多,主要的区别就在于清除引用队列的函数,让我们挑几点重要的去看看它

构造函数

构造函数我们只看重要的部分,节点部分

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

这段的重点在于继承了WeakReference,并且将键作为弱引用,以及队列作为参数调用WeakReference类的构造方法。

    public WeakReference(T referent, ReferenceQueue<? super T> q) {
       # 弱引用使用的是Reference的构造方法
        super(referent, q);
    }

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

从以上几段代码,WeakHashMap是使用队列来实现的!

WeakHashMap的清除过程

在WeakHashMap中,很多方法都包含下面的一个清除函数,用来清除队列中的弱引用

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

重点:
由此可以看出WeakHashMap清除弱引用的流程,采用的是Reference图上面的方法,用一个队列来保存弱引用,然后从WeakHashMap中GC掉存在于ReferenceQueue中的弱引用,同时清除ReferenceQueue中存放的元素。所有上面的铺垫其实都是为了这段的总结。

实例讲解

import java.util.HashMap;
import java.util.WeakHashMap;

public class Test {

    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<>();
        WeakHashMap<String, Integer> weakmap = new WeakHashMap<>();
        //String a = "01";
        //String b = "02";
        String a = new String("11");
        String b = new String("22");
        map.put(a, 1);
        map.put(b, 2);

        weakmap.put(a, 1);
        weakmap.put(b, 2);

        map.remove(a);
        a = null;
        b = null;

        System.gc();
        map.forEach((k, v) -> {
            System.out.println("HashMap: key: "+k+"Value: "+v);
        });

        weakmap.forEach((k, v) -> {
            System.out.println("WeakMap: key: "+k+"Value: "+v);
        });

    }
}

----结果----
HashMap: key: 22Value: 2
WeakMap: key: 22Value: 2

这个例子比较经典,首先a,b都变为null。解除了引用,map手动移除了a,所以除了WeakMap中存在a的引用,别处都不存在了,所以在访问的时候自动清楚了a的引用,weakMap中只存在b的引用。

总结

我们首先从源头Reference类,观察了引用类的生命周期几种状态和GC方法,一种是自动GC一种是使用ReferenceQueue的回收,WeakHashMap采用的是下面一种,队列回收法。关于细节部分未尽详细,但是搞清楚了大概的流程,以及WeakHashMap的运作。全局把握即可,如上都是笔者自己的理解,可能有些地方不够正确,望纠正。

猜你喜欢

转载自blog.csdn.net/qq_41376740/article/details/80366143