弱键集合WeakHashMap的原理以及ConcurrentCache的原理

详细介绍了弱键集合WeakHashMap 的原理,以及tomcat中的ConcurrentCache的原理。

1 WeakHashMap 的原理

基于哈希表的Map接口实现,支持null键和值,但是WeakHashMap具有弱键,可用来实现缓存存储,在进行GC的时候会自动回收键值对。

WeakHashMap 的 Entry 节点继承自 WeakReference。put方法插入键值对时,创建Entry节点时,key被WeakReference引用,get方法获取key的时候,实际上是从WeakReference中获取的。Value正常引用存储,每次创建插入Entry 节点的时候,还会给WeakReference对象关联一个引用队列ReferenceQueue。

/**
 * 已清除的 WeakEntries 的引用队列
 */
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
/**
 * Entry继承了WeakReference,key被WeakReference直接关联
 */
private static class Entry<K, V> extends WeakReference<Object> implements Map.Entry<K, V> {
    V value;
    final int hash;
    Entry<K, V> next;

    /**
     * Creates new entry.
     */
    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;
    }

    //…………
}

key被WeakReference对象引用,它就是一个弱键。根据Java弱引用的特性,被WeakReference引用的对象在没有其他外部引用关联时,在下一次垃圾回收时将会回收该对象,并且其关联的WeakReference对象也会被加入到相关的引用队列中。

如果某个key因为没有其他外部引用被“回收”了getKey()方法就获取不到key了,就会返回null,其对应的Entry也会被加入到相关的引用队列中去了,此时这个Entry也就访问不到了,看起来整个Entry就像被回收了一样,但是此时这个Entry并没有被回收,因为它还被内部table数组引用了。

**无效的Entry怎么被清除呢? **实际上当我们每次需要操作WeakHashMap时,会先清除无效的Entry,位于expungeStaleEntries方法中。table中保存了全部的Entry键值对,而queue中保存被GC回收的Entry键值对,通过比对就能删除table中被GC回收的Entry键值对,这样就能清除无效的Entry了。

2 tomcat的ConcurrentCache

Tomcat的ConcurrentCache就使用了 WeakHashMap 来实现缓存功能。

ConcurrentCache 采取的是分代缓存,其内部保存了两个Map:

  1. 经常使用的对象放入 eden 中,eden 使用 ConcurrentHashMap 实现,不用担心会被回收(伊甸园);
  2. 不常用的对象放入 longterm,longterm 使用 WeakHashMap 实现,这些不常使用的对象会被垃圾收集器回收。
  3. 当调用 get() 方法时,会先从 eden 区获取,如果没有找到的话再到 longterm 获取,当从 longterm 获取到就把对象放入 eden 中,从而保证经常被访问的节点不容易被回收。
  4. 当调用 put() 方法时,如果 eden 的大小超过了 size,那么就将 eden 中的所有对象都放入 longterm 中,利用虚拟机回收掉一部分不经常使用的对象。
public final class ConcurrentCache<K, V> {

    private final int size;

    private final Map<K, V> eden;

    private final Map<K, V> longterm;

    public ConcurrentCache(int size) {
        this.size = size;
        this.eden = new ConcurrentHashMap<>(size);
        this.longterm = new WeakHashMap<>(size);
    }

    public V get(K k) {
        V v = this.eden.get(k);
        if (v == null) {
            synchronized (longterm) {
                v = this.longterm.get(k);
            }
            if (v != null) {
                this.eden.put(k, v);
            }
        }
        return v;
    }

    public void put(K k, V v) {
        if (this.eden.size() >= size) {
            synchronized (longterm) {
                this.longterm.putAll(this.eden);
            }
            this.eden.clear();
        }
        this.eden.put(k, v);
    }
}

如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值