欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-03-31 10:07:25
...

前言

在《深入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

主要的流程

其主要的流程图如下。
新手读源码__java中的4种引用+WeakHashMap的弱引用的底层实现
或者可以这么将,上面的那条路线是程序员手动处理队列的方法,下面是交给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的运作。全局把握即可,如上都是笔者自己的理解,可能有些地方不够正确,望纠正。