概述

WeakHashMap也是Map接口的一个实现类,它与HashMap相似,也是一个哈希表,存储key-value pair,而且也是非线程安全的。不过WeakHashMap并没有引入红黑树来尽量规避哈希冲突带来的影响,内部实现只是数组+单链表。此外,WeakHashMap与HashMap最大的不同之处在于,WeakHashMap的key是“弱键”(weak keys),即当一个key不再正常使用时,key对应的key-value pair将自动从WeakHashMap中删除,在这种情况下,即使key对应的key-value pair的存在,这个key依然会被GC回收,如此以来,它对应的key-value pair也就被从map中有效地删除了。

Java的四种引用

在正式进入WeakHashMap源码之前,我们需要先对“弱引用”有一个基本的认识,为此这里先介绍一下JDK 1.2开始推出的四种引用:

  • 强引用(Strong Reference) 强引用是指在程序代码之中普遍存在的,类似Objective obj = new Object()这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
  • 软引用(Soft Reference) 软引用是用来描述一些还有用但并非必需的对象,对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK 1.2之后,提供了SoftReference类来实现软引用。
  • 弱引用(Weak Reference) 弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一点,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引用。
  • 虚引用(PhantomReference) 虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2之后,提供了PhantomReference类来实现虚引用。

我们说WeakHashMap的key是weak-keys,即是说这个Map实现类的key值都是弱引用。

底层实现

先来看一下WeakHashMap的定义:

public class WeakHashMap<K,V>extends AbstractMap<K,V>implements Map<K,V> {

与HashMap一样继承自AbstractMap类,并特地标注了Map接口,另外,WeakHashMap并没有实现Cloneable接口和Serializable接口。

再来看一下WeakHashMap的重要静态内部类Entry

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) {//把key传给父类WeakReference的构造函数,说明Entry的key是弱引用//同时key值会进入引用队列queue中,等待被处理super(key, queue);//显示定义了value,说明了Entry的value是强引用this.value = value;this.hash  = hash;this.next  = next;}@SuppressWarnings("unchecked")public K getKey() {//在获取key时需要unmaskNull,因为对于null的key,是用WeakHashMap的内部成员属性来表示的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 Objects.hashCode(k) ^ Objects.hashCode(v);}public String toString() {return getKey() + "=" + getValue();}
}

这里用到了unmaskNull()方法,它的作用是用一个空的Object对象来表示null值得key。其源码如下:

private static final Object NULL_KEY = new Object();
/** * 当key为null时,使用NULL_KEY表示key */
private static Object maskNull(Object key) {return (key == null) ? NULL_KEY : key;
}

可以看到Entry<K,V>继承自WeakReference类,那么对于Entry类中需要定义为弱引用的字段,直接传入父类的构造函数即可,如代码中看到的key,这也实现了我们前面说的“弱键”。而value值则赋予了强引用,但这并不影响,我们在稍后会介绍一个WeakHashMap.expungeStaleEntries方法,该方法会把弱键对应的key-value整个赋为null,以帮助GC将其回收。

再来看一下WeakHashMap的重要字段:

  /** * 存储键值对的数组,一般是2的幂 */Entry<K,V>[] table;/** * 键值对的实际个数 */private int size;/** * 扩容的临界值,通过capacity * load factor可以计算出来。超过这个值HashMap将进行扩容 * @serial */private int threshold;/** * 负载因子 */ private final float loadFactor;/** * 引用队列,用于保存会被GC回收的弱键 */private final ReferenceQueue<Object> queue = new ReferenceQueue<>();/** * 记录HashMap被修改结构的次数。 * 修改包括改变键值对的个数或者修改内部结构,比如rehash * 这个域被用作HashMap的迭代器的fail-fast机制中(参考ConcurrentModificationException) */ int modCount;

与HashMap相比,WeakHashMap少了entrySet字段,而多了一个引用队列queue。

WeakHashMap定义的静态全局变量如下:

    private static final int DEFAULT_INITIAL_CAPACITY = 16;/** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30. */private static final int MAXIMUM_CAPACITY = 1 << 30;/** * The load factor used when none specified in constructor. */private static final float DEFAULT_LOAD_FACTOR = 0.75f;

与HashMap相比,缺少了TREEIFY_THRESHOLDUNTREEIFY_THRESHOLDMIN_TREEIFY_CAPACITY三个静态全局变量,它们是用来进行单链表和红黑树转换的,在WeakHashMap中没有实现红黑树,因此不需要这三个变量。

下面再来看一下与弱键实现有关的重要方法expungeStaleEntries

/** * 从哈希表中删除被回收的引用 */
private void expungeStaleEntries() {for (Object x; (x = queue.poll()) != null; ) {synchronized (queue) {@SuppressWarnings("unchecked")Entry<K,V> e = (Entry<K,V>) x;//e为要清理的Entryint 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;elseprev.next = next;// Must not null out e.next;// stale entries may be in use by a HashIteratore.value = null; // 把value赋值为null,帮助GC回收强引用的valuesize--;break;}prev = p;p = next;}}}
}

从WeakHashMap.Entry的源码中我们看到,weak keys的值会被保存到引用队列中,该方法就说将引用队列中保存的弱键对应的Entry从单链表中删除,即删除哈希表中被GC回收了的键值对。在WeakHashMap定义的增、删、改、查方法中,都要调用该方法。

应用场景

缓存

缓存是内存泄漏的一个常见来源,一旦你把对象引用放到缓存中,它就很容易被遗忘掉,从而使得它不再有用之后很长一段时间内仍然留在缓存中。

对于这个问题,有几种可能的解决方案。如果你正好要实现这样的缓存:只要在缓存之外存在对某个项的键的引用,该项就有意义,那么就可以用WeakHashMap代表缓存。当缓存中的项过期之后,它们就会自动被删除(要注意的是,只有当所要的缓存项的生命周期是由该键的外部引用而不是由值决定时,WeakHashMap才有用处)。

Tomcat就用WeakHashMap实现了它的并发缓存ConcurrentCache,源码如下:

package org.apache.tomcat.util.collections;import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;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);}
}

监听器和其他回调

内存泄漏的另一个常见来源是监听器和其他回调。如果你在实现的是客户端注册回调却没有显式地取消注册的API,除非你采取某些动作,否则它们就会积聚。确保回调立即被当作垃圾回收的最佳方法是只保存它们的弱引用(weak reference),可以只将它们保存成WeakHashMap中的键。

Java集合框架源码解读(4)——WeakHashMap相关推荐

  1. 【java集合框架源码剖析系列】java源码剖析之ArrayList

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本. 本博客将从源码角度带领大家学习关于ArrayList的知识. 一ArrayList类的定义: public class Arr ...

  2. Java集合框架源码详解系列(一)

     写在前面:大家好!我是晴空๓.如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正,感谢大家的不吝赐教.我的唯一博客更新地址是:https://ac-fun.blog.csdn.net/.非常 ...

  3. 【java集合框架源码剖析系列】java源码剖析之java集合中的折半插入排序算法

    注:关于排序算法,博主写过[数据结构排序算法系列]数据结构八大排序算法,基本上把所有的排序算法都详细的讲解过,而之所以单独将java集合中的排序算法拿出来讲解,是因为在阿里巴巴内推面试的时候面试官问过 ...

  4. Java集合框架源码解析之ArrayList

    ArrayList 可能是很多人使用得最为频繁的容器类了,ArrayList 实现了 List 接口,是一个有序容器,即存放元素的顺序与添加顺序相同,允许添加相同元素,包括 null ,底层通过数组来 ...

  5. Java集合框架源码剖析:LinkedHashSet 和 LinkedHashMap

    Java LinkedHashMap和HashMap有什么区别和联系?为什么LinkedHashMap会有着更快的迭代速度?LinkedHashSet跟LinkedHashMap有着怎样的内在联系?本 ...

  6. Java集合类框架源码分析 之 LinkedList源码解析 【4】

    上一篇介绍了ArrayList的源码分析[点击看文章],既然ArrayList都已经做了介绍,那么作为他同胞兄弟的LinkedList,当然必须也配拥有姓名! Talk is cheap,show m ...

  7. Java Review - PriorityQueue源码解读

    文章目录 Pre PriorityQueue 概述 PriorityQueue 继承关系 PriorityQueue通过用数组表示的小顶堆实现 时间复杂度 构造函数 方法 add()和offer() ...

  8. Java Review - LinkedList源码解读

    文章目录 Pre 概述 底层数据结构-双向链表 源码解析 构造函数 方法源码分析 getFirst() getLast() remove相关方法 remove(e) remove(index) rem ...

  9. Slim 框架源码解读

    0x00 前言 Slim 是由<PHP The Right Way>作者开发的一款 PHP 微框架,代码量不算多(比起其它重型框架来说),号称可以一下午就阅读完(我觉得前提是熟悉 Slim ...

  10. java 并发框架源码_Java并发编程高阶技术-高性能并发框架源码解析与实战

    Java并发编程高阶技术-高性能并发框架源码解析与实战 1 _0 Z' @+ l: s3 f6 r% t|____资料3 Z9 P- I2 x8 T6 ^ |____coding-275-master ...

最新文章

  1. 患者砍医生事件的一些胡言乱语
  2. 笔记本启动与关闭自带键盘
  3. iOS开发-多层嵌套block中如何使用__weak和__strong
  4. 计算机的组成结构6,计算机组成及结构.6.ppt
  5. php找不到intl,php_intl.dll找不到指定模块怎么办
  6. k8s集群资源监控-搭建监控平台(prometheus和grafana)---K8S_Google工作笔记0053
  7. NOIP2016D2T2 蚯蚓
  8. matlab 系数矩阵存储,用Matlab对矩阵进行LU分解法 值得收藏
  9. 一图看懂ADSL拨号服务器
  10. JAVA圆和正方形组合图形_关于java:图形数据流组合框架
  11. 【 js 基础 】 setTimeout(fn, 0) 的作用
  12. 计算机打字无限长页面,Win10电脑键盘自动无限输入怎么办
  13. 【flutter】tabbar文字抖动处理方案
  14. Android项目CJM9之新闻浏览模块的实现
  15. 支付宝支付学习:蚂蚁沙箱环境是什么?怎样使用蚂蚁沙箱环境?
  16. Word文档使用Mathtype如何实现公式自动居中并右对齐编号?
  17. 基于MP2307的FPGA实验系统供电设计
  18. 新智慧杂志新智慧杂志社新智慧编辑部2022年第9期目录
  19. Android中按钮的点击事件的四种写法
  20. 行业轮动策略(附策略源码)

热门文章

  1. 16. Magento模板及布局文件中引用Static Block的方法
  2. 用TestComplete实现一个关键字驱动测试框架
  3. Jmeter集成Jira提交缺陷
  4. 实验1-6 输出带框文字
  5. javaweb项目静态资源被拦截的解决方法
  6. Python裁剪图片,游戏大图裁小图
  7. mysql中kill掉所有锁表的进程
  8. C#使用OpcNetApi.dll和OpcNetApi.Com.dll操作OPC
  9. 如果Google统治世界[组图]
  10. 序列化和反序列化(七)——Java对象的网络传输(二)