WeakHashMap源码分析

本文先介绍WeakHashMap的四种引用,然后再从源码角度分析WeakHashMap的继承结构,基本属性等内容。

WeakHashMap并没有什么特殊的数据结构,存在本身主要是为了优化JVM,使得JVM的垃圾回收器(GC)能够更“智能”地回收“无用”的对象。WeakHashMap涉及到Java中的引用类型问题,下面具体介绍Java的四种引用类型。

  • 四种引用

1、强引用(StrongReference)

功能:一个对象如果只有强引用,那么垃圾回收器绝不会回收它,即使当内存不足时,JVM宁愿抛出内存不足的异常,也不会去回收这些对象。

使用场景:平常大部分都会使用强引用,除非显性释放对象(置为null),否则GC不会回收该对象。

由于强引用的一些特性,Java还提供了另外三种引用:软引用,弱引用,虚引用;这三种引用都位于java.lang.ref包中,继承于Reference类,以下将对Reference的源码进行简要分析,再介绍这三种引用。

Reference源码分析:

基本属性

private T referent;    //引用指向的对象     /* Treated specially by GC */
ReferenceQueue<? super T> queue; //ReferenceQueue类,适当的时候检测到对象的可达性发生改变后,垃圾回收器就将已注册的引用对象添加到此队列中。
Reference next;
transient private Reference<T> discovered;  /* used by VM */
private static Reference pending = null;

构造方法

  Reference(T referent) {
        this(referent, null);
    }

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

在源码中,我们也可以看到Reference有4种状态: Active,Pending,Enqueued,Inactive;

关于Active:基本新创建的对象都是这个状态,在 GC 检测到引用对象已经到达合适的可达性状态时,GC 会根据引用对象是否在创建时制定ReferenceQueue参数进行状态转移,如果指定了,那么转移到Pending,如果没指定,转移到Inactive;在该状态中:

//若构造参数中没有指定queue,则queue为ReferenceQueue.NULL,否则为构造参数传递的queue
   Active: queue = ReferenceQueue || queue =ReferenceQueue.NULL
           next = null.

关于 Pending:pending-Reference列表中的引用都是这个状态,它们等着被内部线程ReferenceHandler处理(会调用ReferenceQueue.enqueue方法)。没有注册的实例不会进入这个状态。在这个状态中:

//构造参数传递的queue
   Pending: queue = ReferenceQueue
           next = 该queue的下一个引用,若为该队列的最后一个,则为this;

关于Enqueued:调用ReferenceQueue.enqueued方法后的引用处于这个状态中。没有注册的实例不会进入这个状态。在这个状态中

enqueued: queue = ReferenceQueue.ENQUEUED
        next = 该queue的下一个引用,若为该队列的最后一个,则为this;

关于Inactive:最终状态,处于这个状态的引用对象,状态不会在改变。在这个状态中

Inactive: queue = ReferenceQueue.NULL
        next = this;

2、软引用(SoftReference)

功能:如果一个对象只有软引用,则在内存足够的情况下,不会进行回收,当内存不足时,GC会进行回收操作;

应用场景:可用于有可能会在创建后使用的对象,为了内存消耗会使用软引用;

3、弱引用(WeakReference)

功能:弱引用的生命周期比软引用更短,GC在扫描时发现对象只有弱引用,不论内存是否足够,都会进行回收;

使用场景:适用于生命周期更短,对内存更加敏感的场景中,比如占用内存很大的Map,java API中就提供了WeakHashMap使用,就会使得大Map被及时清理掉。

4、虚引用 (PhantomReference)

功能:形同虚设,保存对象的能力最弱,不会影响对象的生命周期,作用是跟踪GC收集对象的活动情况;

使用场景:判断对象是否被回收了

PhantomReference必须要和ReferenceQueue联合使用,SoftReference和WeakReference可以选择和ReferenceQueue联合使用也可以不选择,这使他们的区别之一。

 

  • 继承结构

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

继承于AbstractMap类,实现了Map接口;区别于HashMap,没有实现Cloneable和Serializable接口,不能实现拷贝和序列化;

 

  • 基本属性

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;

static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE;

 

  • 构造方法

//1 指定初始容量和加载因子
public WeakHashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Initial Capacity: "+
                initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;

    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal Load factor: "+
                loadFactor);
    int capacity = 1;
    while (capacity < initialCapacity)
        capacity <<= 1;
    table = newTable(capacity);
    this.loadFactor = loadFactor;
    threshold = (int)(capacity * loadFactor);
    useAltHashing = sun.misc.VM.isBooted() &&
            (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
}
//2 指定初始容量,加载因子默认
public WeakHashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
//3 默认构造方法
public WeakHashMap() {
    this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//4 子Map
public WeakHashMap(Map<? extends K, ? extends V> m) {
    this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
            DEFAULT_INITIAL_CAPACITY),
            DEFAULT_LOAD_FACTOR);
    putAll(m);
}

 put()

  • 核心方法

    public V put(K key, V value) {
    //对key是否为空进行检查,如果为null,用new的Object进行替换
        Object k = maskNull(key);
        int h = hash(k);
        Entry<K,V>[] tab = getTable();
        int i = indexFor(h, tab.length);
//检查此key是否存在,存在则更新value,不存在则将该节点头插到该位置
        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;
    }
//检查key是否为null,为空用new的Object进行替换
    private static Object maskNull(Object key) {
        return (key == null) ? NULL_KEY : key;
        // private static final Object NULL_KEY = new Object();
    }

其他方法与HashMap基本一致,主要差别在于,WeakHashMap对于key是否为null进行了检查,为空的话,将其替换为new的Object常量;put和get方法中的getTable都会调用expungeStaleEntries()

private Entry<K,V>[] getTable() {
    expungeStaleEntries();
    return table;
}

还有一个size()方法,在返回节点个数时,还有一步操作:expungeStaleEntries() ,删除过时节点,然后再返回个数;

public int size() {
    if (size == 0)
        return 0;
    expungeStaleEntries();
    return size;
}
//该方法在增删改查方法中基本上都调用了
    private void expungeStaleEntries() {
    //所有的Entry在创建时,都先传入该queue
        for (Object x; (x = queue.poll()) != null; ) {
            synchronized (queue) {
                @SuppressWarnings("unchecked")
                Entry<K, V> e = (Entry<K, V>) x;//e为要清理的对象
                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,帮助GC回收强引用的value
                        e.value = null; // Help GC
                        size--;
                        break;
                    }
                    prev = p;
                    p = next;
                }
            }
        }
    }
  • 特点

 随时间推移,键值对会减少   ->  GC垃圾回收

               WeakHashMap与ReferenceQueue结合使用,使用步骤:

1、创建WeakHashMap将键值对存放在数组链表上;

2、当发生GC时会判断key是否有引用,若没有引用,就会回收key,意味着从集合中回收,回收同时,将该对象加入ReferenceQueue;

3、在操作WeakHashMap时,会首先同步table和Queue,table存放的是所有键值对, Queue存放的是被回收的键值对,在table中删除Queue中有的键值对;

  • 总结

WeakHashMap和HashMap一样,WeakHashMap也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以为null。不过WeakHashMap的键时“弱键”,当WeakHashMap某个键不再正常使用时,会被从WeakHashMap自动删除。更精确的说,对于一个给定的键,其映射的存在并不能阻止垃圾回收器对该键的丢弃,这就使该键称为被终止的,被终止,然后被回收,这样,这就可以认为该键值对应该被WeakHashMap删除。因此,WeakHashMap使用了弱引用作为内部数据的存储方案,,WeakHashMap可以作为简单缓存表的解决方案,当系统内存不足时,垃圾收集器会自动的清除没有在任何其他地方被引用的键值对。如果需要用一张很大的Map作为缓存表时,那么可以考虑使用WeakHashMap。

总结参考自博文:https://blog.csdn.net/u010412719/article/details/52034079 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值