WeakHashMap总结

来源:互联网 发布:远东水利造价软件 编辑:程序博客网 时间:2024/06/08 16:11

WeakHashMap利用WeakReference的弱引用特性让用户在使用的过程中不会因为没有释放Map中的资源而导致内存泄露。WeakHashMap实现了Map接口,使用方式和其他的Map相同,需要注意的是get方法和size方法的使用。在介绍WeakHashMap之前需要先介绍一下Reference的概念。

1、Reference的实现
public abstract class Reference<T> {    private T referent;   //对应的引用对象    volatile ReferenceQueue<? super T> queue; //引用队列,初始化的时候从外部传入    Reference next;  //Reference本身就是链表中的一个节点,next指向队列中的下一个节点    transient private Reference<T> discovered;  /* used by VM */    static private class Lock { }     private static Lock lock = new Lock(); //用来同步锁操作}

Reference类有四个直接子类,PhantomReference、FinalReference、SoftReference、WeakReference。其中SoftReference比WeakReference约束要强一些,当内存不够用的时候jvm才会将对应引用的对象删除掉,而WeakReference在对象引用不可达的时候就会被jvm清理掉,PhantomReference(幽灵引用)和约束更弱,get方法永远都返回null,无法像前两者一样可以通过get方法获取一个强引用,PhantomReference只能用来观察gc后的引用队列,不能用来获取引用对象。
无论哪种reference,都有一个重要的对象来跟踪对象的gc动作,这个就是ReferenceQueue。

2、ReferenceQueue的实现
public class ReferenceQueue {    static private class Lock { }; //锁对象,用来同步队列操作    private Lock lock = new Lock();     private volatile Reference<? extends T> head = null; //头结点    private long queueLength = 0; //队列长度}

a、入队操作

boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */   synchronized (lock) {        ReferenceQueue<?> queue = r.queue; //获取reference之前的队列,如果没有绑定队列,那就不需要入队        if ((queue == NULL) || (queue == ENQUEUED)) {            return false;        }        assert queue == this;        r.queue = ENQUEUED;        r.next = (head == null) ? r : head; //把r从链表的头部插入        head = r; //头结点指向r        queueLength++; //队列长度+1        if (r instanceof FinalReference) {            sun.misc.VM.addFinalRefCount(1);        }        lock.notifyAll(); //唤醒删除线程删除头结点        return true;    }}

出队列的操作相似,每个reference就是链表中的一个节点,next指向下一个reference节点。
b、删除操作

public Reference<? extends T> remove(long timeout)    throws IllegalArgumentException, InterruptedException{    if (timeout < 0) {        throw new IllegalArgumentException("Negative timeout value");    }    synchronized (lock) {        Reference<? extends T> r = reallyPoll(); //尝试获取队列的头结点        if (r != null) return r; //获取成功,返回        long start = (timeout == 0) ? 0 : System.nanoTime(); //如果超时时间不为0,就记录一下当前的开始时间        for (;;) {            lock.wait(timeout);            r = reallyPoll();            if (r != null) return r;            if (timeout != 0) {                long end = System.nanoTime();                timeout -= (end - start) / 1000_000; //计算一下等待的时间                if (timeout <= 0) return null; //确认等待是否是超时,如果是就返回,否则就认为是有入队请求唤醒当前线程,但是当前线程尝试删除头结点失败了(被其他线程删除了),那么继续尝试删除头结点,再次执行循环中的内容直到超时                start = end;            }        }    }}

ReferenceQueue是线程安全的,出队入队操作都由lock对象来保证线程安全,当用户线程和jvm线程同时访问ReferenceQueue的时候不会出现并发问题。

WeakHashMap

WeakHashMap内部同样是通过一个数组来实现存储,解决冲突的方式也是使用拉链法,weakHashMap中重新定义了Entry类来存储kv键值对,Entry的实现也是实现WeakHashMap特性的关键。

1、Entry的定义

WeakHashMap重新定义了一个entry,这个entry继承了WeakReference类并且实现了Entry接口,使用该Entry存储键值对不会产生强引用,jvm在垃圾回收的时候不会认为该引用是强引用,会正常的回收对象,Entry的定义如下

private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {  V value;          //存储的value      final int hash;   //hash值  Entry<K,V> next;  //拉链法解决冲突,形成单链表  Entry(Object key, V value,ReferenceQueue<Object> queue, int hash, Entry<K,V> next) {  super(key, queue);   //这里的引用对象是key,跟踪的是key对象的垃圾回收  this.value = value;  this.hash  = hash;  this.next  = next;  }  ...}

Entry的构造器需要传入ReferenceQueue,这个queue就可以用来监控全局的Entry被清理的情况。
2、清理操作
当对象被垃圾回收的时候,当前Map需要删除掉对应的Entry,因为Entry此时指向的对象已经被回收,所以需要找到被jvm回收的对象对应的Entry并且将Entry对象从Map中移除掉,实现方式是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,根据entry获取entry链表在table中的索引            Entry<K,V> prev = table[i]; //找到entry链表的头结点            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;                    e.value = null; // Help GC                    size--;                    break;                }                prev = p;                p = next;            }        }    }}

该方法在map中的getTable()、size()和resize()方法中被调用,每次put或是get的时候都会先执行getTable()方法,因此每次读写数据的时候都会清理掉无用的entry,所以用户不会获取到被垃圾回收的清理entry,因此,每次用户调用get方法或是size方法的时候,都会触发清理操作,所以每次返回的结果可能都不相同,因为内部的entry持有的对象已经被jvm回收。

线程安全问题

WeakHashMap作为容器本事不是线程安全的,但是在使用过程中,在单线程环境中ReferenceQueue会被当前业务线程和jvm线程访问,是ReferenceQueue是线程安全的。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 瓷砖用刀子划了怎么办 陶瓷洗手台裂了怎么办 洗车泵水管坏了怎么办 印胶浆里面渗入了发泡浆怎么办? 管子断在水管里怎么办 衣服上的织带缩水怎么办 真丝衣服拔缝了怎么办 顾客说衣服太花怎么办 铝和碱反应变黑怎么办 40度高温多肉怎么办 沾到医用蓝药水怎么办? 裤子弄上泡沫胶怎么办 苍蝇纸粘衣服上怎么办 苍蝇胶沾衣服上怎么办 灯带为什么不亮怎么办 苹果6比屏幕变黄怎么办 雷腾键盘锁了怎么办 自吸泵电机不转怎么办 孕38周胎儿偏小怎么办 被火烧黑的铁怎么办 锅被烟熏黑了怎么办 墙壁被烟熏黑了怎么办 壁纸被烟熏黑了怎么办 空调被烟熏黑了怎么办 牙被烟熏黑了怎么办 尖头鞋把尖折了怎么办 腰椎固定手术钢钉断了怎么办 脚被钢钉扎了怎么办 皮帘子有胶了怎么办 12v插口没有电怎么办 吃了一颗聚乙烯醇怎么办 立式注塑机产品粘膜怎么办 被小松鼠咬了怎么办 被宠物松鼠咬了怎么办 手被松鼠咬出血怎么办 银联认证码失败怎么办 国际汇款触发合规查询怎么办 外面的网线断了怎么办 网线被别的车挂断了怎么办 施工挖断军用光缆怎么办 不小心挖断光缆怎么办