亲宝软件园·资讯

展开

WeakHashMap

小高飞 人气:0

WeakHashMap

今天在阅读《Effective Java》的【消除过期的对象引用】一章中,讲到了WeakHashMap,发现对该类不怎么熟悉,因此记录下整理的相关知识。

Java的引用类型

在学习WeakHashMap之前,得知道Java的4个引用类型:

  • 强引用:如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。比如String str = "hello"这时候str就是一个强引用。

  • 软引用:内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。

  • 弱引用:如果一个对象具有弱引用,在垃圾回收时候,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。

  • 虚引用:如果一个对象具有虚引用,就相当于没有引用,在任何时候都有可能被回收。使用虚引用的目的就是为了得知对象被GC的时机,所以可以利用虚引用来进行销毁前的一些操作,比如说资源释放等。

而WeakHashMap的键就是弱引用,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。

 

WeakReference

在WeakHashMap中真正实现弱引用的是WeakReference类,该类有两个构造方法:

public class WeakReference<T> extends Reference<T> {
    public WeakReference(T referent) {
        super(referent);
    }
​
    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}
  • WeakReference(T referent):referent就是被弱引用的对象(注意区分弱引用对象和被弱引用的对应,弱引用对象是指WeakReference的实例或者其子类的实例),比如有一个Apple实例apple,可以如下使用,并且通过get()方法来获取apple引用。也可以再创建一个继承WeakReference的类来对Apple进行弱引用,下面就会使用这种方式。

  • WeakReference(T referent, ReferenceQueue<? super T> q):与上面的构造方法比较,多了个ReferenceQueue,在对象被回收后,会把弱引用对象,也就是WeakReference对象或者其子类的对象,放入队列ReferenceQueue中,注意不是被弱引用的对象,被弱引用的对象已经被回收了。

 

WeakHashMap

WeakHashMap的Entry继承了WeakReference,而后在构造方法中调用父类WeakReference的构造方法并将key设为被弱引用对象。

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

 

 

而在WeakHashMap内部有一个expungeStaleEntries函数,在这个函数内部实现移除其内部不用的entry从而达到的自动释放内存的目的。因此我们每次访问WeakHashMap的时候,都会调用这个expungeStaleEntries函数清理一遍。因为涉及到链表,所以下面代码讲解中将Entry称为链表中的一个节点。

private void expungeStaleEntries() {
    //如果引用队列不为空,则说明有被弱引用的对象被GC回收,则需释放被弱引用的对象的Entry
    for (Object x; (x = queue.poll()) != null; ) {
        synchronized (queue) {
            @SuppressWarnings("unchecked")
            Entry<K,V> e = (Entry<K,V>) x;
            //获取该entry在Entry集合table中的索引位置
            int i = indexFor(e.hash, table.length);
            
            //从table[i]节点处开始往后截取一段链表
            Entry<K,V> prev = table[i];
            //创建p指向当前节点,初始和前一个节点prev是同一个节点
            Entry<K,V> p = prev;
            while (p != null) {
                //创建next节点指向当前节点p的下一个节点
                Entry<K,V> next = p.next;
                //如果当前节点等于被弱引用的对象的节点e,则需让当前节点失去引用,让e节点进入到下一波GC回收中
                if (p == e) {
                //如果前一个节点也等于被弱引用的对象的节点e,则说明当前节点p和前一个节点prev是同一个节点,即需被弱引用的对象的节点e是截取链表段的头节点,则将table[i]设为下一节点,节点e失去引用
                    if (prev == e)
                        table[i] = next;
          //反之,则说明被弱引用的对象的节点e是前一个节点prev的下一个节点,则将prev的下一个节点设为节点e的下一个节点,节点e失去引用
                    else
                        prev.next = next;
                    e.value = null; //将e的值设为null,加速垃圾回收
                    size--;
                    break;
                }   
                prev = p;
                p = next;
            }
        }
    }
}



加载全部内容

相关教程
猜你喜欢
用户评论