WeakHashMap粗解

本文详细解析了WeakHashMap的工作原理,包括其内部实现机制、弱引用及其与ReferenceQueue的关系,并通过示例说明了如何利用WeakHashMap自动回收内存。

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

转载自:http://blog.youkuaiyun.com/u010412719/article/details/52035792
              http://blog.youkuaiyun.com/z69183787/article/details/54581740
              http://blog.youkuaiyun.com/z69183787/article/details/54582249

      首先得了解Reference与ReferenceQueue,可以参看:http://blog.youkuaiyun.com/zero__007/article/details/77984272


       WeakHashMap 继承于AbstractMap,实现了Map接口。 和HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null。
      WeakHashMap的键是“弱键”,里面存放了键对象的弱引用,当某个键不再正常使用时,会从WeakHashMap中被自动移除。当一个键对象被垃圾回收,那么相应的值对象的引用会从Map中删除。WeakHashMap能够节约存储空间,可用来缓存那些非必须存在的数据。
       WeakHashMap的“弱键”就是用WeakReference、ReferenceQueue来实现的。WeakHashMap内部的Entry<K,V>源码如下:
    /**
     * 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;
        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);// WeakReference(referent, q)
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }

        @SuppressWarnings("unchecked")
        public K getKey() {
            return (K) WeakHashMap.unmaskNull(get());//Reference.get()
        }

        public V getValue() {
            return value;
        }

        ……
       在WeakHashMap中,key都是WeakReference类型,在WeakHashMap.expungeStaleEntries方法中:
    /**
     * 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;
                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; // Help GC
                        size--;
                        break;
                    }
                    prev = p;
                    p = next;
                }
            }
        }
    }
       也许会有个疑问,明明Entry 使用 WeakReference super方法初始化时,queue中加入的是key对象,为何poll的时候对象x的类型为Entry<X,Y>?这是因为key的引用发生变化后(没有再被引用),reference实例会进入Pending状态,之后ReferenceHandler线程会将进入Pending状态的reference,调用q.enqueue()方法,加入到queue中,那么queue.poll()的时候,返回的即reference,也就是Entry<X,Y>对象。
       通过对queue的遍历,获取所有需要清除的“弱引用key”,然后再WeakHashMap中删除对应键值对。
       需要注意,WeakHashMap的Key是弱引用,Value不是。WeakHashMap不会自动释放失效的弱引用,仅当包含了expungeStaleEntries()的共有方法被调用的时候才会释放。简单示例:
public static void main(String args[]) {
	WeakHashMap<String, String> map = new WeakHashMap<String, String>();
	map.put(new String("1"), "1");
	map.put("2", "2");
	String s = new String("3");
	map.put(s, "3");
	while (map.size() > 0) {
		try {
			Thread.sleep(500);
		} catch (InterruptedException ignored) {
		}
		System.out.println("Map Size:" + map.size());
		System.out.println(map.get("1"));
		System.out.println(map.get("2"));
		System.out.println(map.get("3"));
		System.gc();
	}
}
       通过运行结果会发现,map.get("1")对应的value会被GC,因为new String("1")没有被引用,map.get("2")对应的key被放在常量池中,map.get("3")对应的key还有s在引用。

       WeakHashMap是主要通过expungeStaleEntries这个函数的来实现移除其内部不用的Entry从而达到的自动释放内存的目的的。基本上只要对WeakHashMap的内容进行访问就会调用这个函数,从而达到清除其内部不在为外部引用的Entry。但是如果生成了WeakHashMap,而在GC以前又不曾访问该WeakHashMap,那不是就不能释放内存了吗?
       对应的两个测试案例:
List<WeakHashMap<byte[][], byte[][]>> maps = new ArrayList<WeakHashMap<byte[][], byte[][]>>();
for (int i = 0; i < 1000; i++) {
    WeakHashMap<byte[][], byte[][]> d = new WeakHashMap<byte[][], byte[][]>();
    d.put(new byte[1000][1000], new byte[1000][1000]);
    maps.add(d);
    System.gc();
    System.err.println(i);
}
List<WeakHashMap<byte[][], byte[][]>> maps = new ArrayList<WeakHashMap<byte[][], byte[][]>>();
for (int i = 0; i < 1000; i++) {
    WeakHashMap<byte[][], byte[][]> d = new WeakHashMap<byte[][], byte[][]>();
    d.put(new byte[1000][1000], new byte[1000][1000]);
    maps.add(d);
    System.gc();
    System.err.println(i);
    for (int j = 0; j < i; j++) {
	System.err.println(j + " size" + maps.get(j).size());
    }
}
       第一个例子会会造成OOM异常,而第二个不会。因为size()里面触发了expungeStaleEntries操作,它将 ReferenceQueue中的 WeakReference对象从map中删除了,对应着value也一并删除了,使得value也被GC回收了。

       总结来说:WeakHashMap并不是你啥也干他就能自动释放内部不用的对象的,而是在你访问它的内容的时候释放内部不用的对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值