WeakHashMap
前导知识:
强,软,弱,虚引用区别?
强引用是使用最普遍的引用:Object o=new Object(); 即使发生OOM也不会被GC
将对象的引用显示地置为null:o=null; // 帮助垃圾收集器回收此对象
软引用用来描述一些还有用但是并非必须的对象,在Java中用java.lang.ref.SoftReference类来表示。对于软引用关联着的对象,只有在内存不足的时候JVM才会回收该对象。因此,这一点可以很好地用来解决OOM的问题,并且这个特性很适合用来实现缓存。
String str=new String("abc"); // 强引用 SoftReference<String> softRef=new SoftReference<String>(str); // 软引用
弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。java中用WeakReference来实现弱引用。
如果这个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象。 比如ThreadLocal中Entry对象的key就是弱引用。
虚引用也称为幻影引用:一个对象是都有虚引用的存在都不会对生存时间都构成影响,也无法通过虚引用来获取对一个对象的真实引用。唯一的用处:能在对象被GC时收到系统通知,JAVA中用PhantomReference来实现虚引用。
软(弱、虚)引用必须和一个引用队列(ReferenceQueue)一起使用,当gc回收这个软(弱、虚)引用引用的对象时,会把这个软(弱、虚)引用放到这个引用队列中。
虚引用用来标识对象的生命周期,在其指向的对象从内存中移除掉之后才会加入到引用队列。
WeakHashMap特性:
WeakHashMap是一种弱引用map,内部的key会存储为弱引用,当jvm gc的时候,如果这些key没有强引用存在的话,会被gc回收掉,下一次当我们操作map的时候会把对应的Entry整个删除掉,基于这种特性,WeakHashMap特别适用于缓存处理。
它的扩容:
private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) { // 遍历旧桶 for (int j = 0; j < src.length; ++j) { Entry<K,V> e = src[j]; src[j] = null; while (e != null) { Entry<K,V> next = e.next;//保存它的后面的entry Object key = e.get(); // 如果key等于了null就清除,说明key被gc清理掉了,则把整个Entry清除 if (key == null) { e.next = null; // Help GC e.value = null; // " " size--; } else { // 否则就计算在新桶中的位置并把这个元素放在新桶对应链表的头部 int i = indexFor(e.hash, dest.length); e.next = dest[i]; dest[i] = e; } e = next; } } }
(1)判断旧容量是否达到最大容量;
(2)新建新桶并把元素全部转移到新桶中;
(3)如果转移后元素个数不到扩容门槛的一半,则把元素再转移回旧桶,继续使用旧桶,说明不需要扩容;
(4)否则使用新桶,并计算新的扩容门槛;
(5)转移元素的过程中会把key为null的元素清除掉,所以size会变小;主要记住这个删除元素的过程
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<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; } } } }
(1)当key失效的时候gc会自动把对应的Entry添加到这个引用队列中;
(2)所有对map的操作都会直接或间接地调用到这个方法先移除失效的Entry,比如getTable()、size()、resize();
(3)这个方法的目的就是遍历引用队列,并把其中保存的Entry从map中移除掉,具体的过程请看类注释;
(4)从这里可以看到移除Entry的同时把value也一并置为null帮助gc清理元素,防御性编程。
测试使用:
public class WeakHashMapTest { public static void main(String[] args) { Map<String, Integer> map = new WeakHashMap<>(3); // 放入3个new String()声明的字符串 map.put(new String("1"), 1); map.put(new String("2"), 2); map.put(new String("3"), 3); // 放入不用new String()声明的字符串 map.put("6", 6); // 使用key强引用"3"这个字符串 String key = null; for (String s : map.keySet()) { // 这个"3"和new String("3")不是一个引用 if (s.equals("3")) { key = s; } } // 输出{6=6, 1=1, 2=2, 3=3},未gc所有key都可以打印出来 System.out.println(map); // gc一下 System.gc(); // 放一个new String()声明的字符串 map.put(new String("4"), 4); // 输出{4=4, 6=6, 3=3},gc后放入的值和强引用的key可以打印出来 System.out.println(map); // key与"3"的引用断裂 key = null; // gc一下 System.gc(); // 输出{6=6},gc后强引用的key可以打印出来 System.out.println(map); } }
在这里通过new String()声明的变量才是弱引用,使用”6″这种声明方式会一直存在于常量池中,不会被清理,所以”6″这个元素会一直在map里面,其它的元素随着gc都会被清理掉。
参考:
本文来自博客园,作者:LeeJuly,转载请注明原文链接:https://www.cnblogs.com/peterleee/p/11000963.html

浙公网安备 33010602011771号