Java 弱引用 - WeakHashMap

本文深入探讨Java中的弱引用(WeakReference)概念及其应用场景,包括弱引用的基本原理、如何使用弱引用实现缓存管理,以及弱引用与垃圾回收机制之间的交互方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java 弱引用 - WeakHashMap

Java 弱引用

Java中有如下四种类型的引用:

  • 强引用(Strong Reference)
  • 弱引用(WeakReference)
  • 软引用(SoftReference)
  • 虚引用(PhantomReference)

在Java里, 当一个对象o被创建时, 它被放在Heap里. 当GC运行的时候, 如果发现没有任何引用指向o, o就会被回收以腾出内存空间. 或者换句话说, 一个对象被回收, 必须满足两个条件: 1)没有任何引用指向它 2)GC被运行。

在现实情况写代码的时候, 我们往往通过把所有指向某个对象的referece置空来保证这个对象在下次GC运行的时候被回收 (可以用java -verbose:gc来观察gc的行为)

Object c = new Car();  
c=null;  

但是, 手动置空对象对于程序员来说, 是一件繁琐且违背自动回收的理念的.  对于简单的情况, 手动置空是不需要程序员来做的, 因为在java中, 对于简单对象, 当调用它的方法执行完毕后, 指向它的引用会被从stack中popup, 所以他就能在下一次GC执行时被回收了.

但是, 也有特殊例外. 当使用cache的时候, 由于cache的对象正是程序运行需要的, 那么只要程序正在运行, cache中的引用就不会被GC(或者说, cache中的reference拥有了和主程序一样的life cycle)。 那么随着cache中的reference越来越多, GC无法回收的object也越来越多, 无法被自动回收。当这些object需要被回收时, 回收这些object的任务只有交给程序编写者了。然而这却违背了GC的本质(自动回收可以回收的objects).

所以, java中引入了weak reference. 相对于前面举例中的strong reference:

Object c = new Car(); //只要c还指向car object, car object就不会被回收  

当一个对象仅仅被weak reference指向, 而没有任何其他strong reference指向的时候, 如果GC运行, 那么这个对象就会被回收。weak reference的语法是:

WeakReference<Car> weakCar = new WeakReference(Car)(car);  

当要获得weak reference引用的object时, 首先需要判断它是否已经被回收:

weakCar.get();  

如果此方法为空, 那么说明weakCar指向的对象已经被回收了.

 

java.lang.ref.Reference

java.lang.ref.Reference 为 软(soft)引用、弱(weak)引用、虚(phantom)引用的父类。

下面简单的分析下 Reference 的源码。其构造函数为,

//referent 为引用指向的对象
Reference(T referent) {
    this(referent, null);
}

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

第二个构造函数需要提供一个 ReferenceQueue 对象,也就是引用队列。在weak reference指向的对象被回收后, weak reference本身其实也就没有用了。java提供了一个ReferenceQueue来保存这些所指向的对象已经被回收的reference 。

 

ReferenceHandler 线程

// eg1
public static void test() throws Exception{
    Object o = new Object();
    // 默认的构造函数,会使用ReferenceQueue.NULL 作为queue
    WeakReference<Object> wr = new WeakReference<Object>(o);
    System.out.println(wr.get() == null);
    o = null;
    System.gc();
    System.out.println(wr.get() == null);
}

这个线程在Reference类的static构造块中启动,并且被设置为高优先级和daemon状态。此线程要做的事情,是不断的检查pending 是否为null,如果pending不为null,则将pending进行enqueue,否则线程进入wait状态。

通过这2点,我们来看整个过程:

pending是由jvm来赋值的,当Reference内部的referent对象的可达状态改变时,jvm会将Reference对象放入pending链表。

结合代码eg1中的 o = null; 这一句,它使得o对象满足垃圾回收的条件,并且在后边显式的调用了 System.gc(),垃圾收集进行的时候会标记WeakReference所referent的对象o为不可达(使得wr.get()==null),并且通过 赋值给pending,触发ReferenceHandler线程处理pending

ReferenceHandler线程要做的是将pending对象enqueue,但默认我们所提供的queue,也就是从构造函数传入的是null,实际是使用了ReferenceQueue.NULLHandler线程判断queue为ReferenceQueue.NULL则不进行操作,只有非ReferenceQueue.NULL的queue才会将Reference进行enqueue。

ReferenceQueue.NULL相当于我们提供了一个空的Queue去监听垃圾回收器给我们的反馈,并且对这种反馈不做任何处理。要处理反馈,则必须要提供一个非ReferenceQueue.NULL的queue。

 

WeakHashMap.Entry

WeakHashMap与HashMap的签名与构造函数一样,这里就不介绍了,这里重点介绍下Entry这个内部对象,因为其保存具体key-value对,把它弄清楚了,其他的就问题不大了。

/**
 * The entries in this hash table extend WeakReference, using its main ref
 * field as the key.
 */
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
    V value;
    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;
    }

    public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;
        K k1 = getKey();
        Object k2 = e.getKey();
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
            V v1 = getValue();
            Object v2 = e.getValue();
            if (v1 == v2 || (v1 != null && v1.equals(v2)))
                return true;
        }
        return false;
    }

    public int hashCode() {
        K k = getKey();
        V v = getValue();
        return ((k==null ? 0 : k.hashCode()) ^
                (v==null ? 0 : v.hashCode()));
    }

    public String toString() {
        return getKey() + "=" + getValue();
    }
}

 

WeakHashMap.expungeStaleEntries

WeakHashMap的 put, size, clear 都会间接或直接的调用到 expungeStaleEntries()方法。

expungeStaleEntries此方法的作用就是将 queue中陈旧的Reference进行删除,因为其内部的referent都已经不可达了。所以也将这个WeakReference包装的key从map中删除。

/**
 * Expunges stale entries from the table.
 */
private void expungeStaleEntries() {
    for (Object x; (x = queue.poll()) != null; ) {
        synchronized (queue) {
            @SuppressWarnings("unchecked")
            Entry<K, V> e = (Entry<K, V>) x;  // e entry 实体,虚引用实例
            int i = indexFor(e.hash, table.length);

            Entry<K, V> prev = table[i];
            Entry<K, V> p = prev;
            while (p != null) {  // while 循环遍历冲突链
                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;
            }
        }
    }
}

参考:http://hongjiang.info/java-referencequeue/

http://puretech.iteye.com/blog/2008663

=============END=============

转载于:https://my.oschina.net/xinxingegeya/blog/916710

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值