Java之WeakHashMap浅析

230 阅读2分钟

在JDK 1.8源码中,WeakHashMap有如下类注释。

Hash table based implementation of the Map interface, with weak keys. An entry in a WeakHashMap will automatically be removed when its key is no longer in ordinary use.

基于哈希表的Map接口实现,带有弱引用的键。当WeakHashMap中的键不再被使用时,该项将被自动删除。

本文将结合源码,带你了解它的底层实现。

1 数据结构

// 默认的初始容量
private static final int DEFAULT_INITIAL_CAPACITY = 16;

// 默认负载因子
private static final float DEFAULT_LOAD_FACTOR = 0.75f;

// 哈希表,必要时扩容,长度增长1倍
Entry<K,V>[] table;

// 阈值,capacity * load factor
private int threshold;

// 负载因子
private final float loadFactor;

// 被gc回收的弱引用key,对应的entry将放入queue中
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

内部类Entry,是WeakReference的子类,将键包装成弱引用。 通过next属性,在哈希冲突时形成单链表。 image.png image.png

2 主要方法实现

2.1 构建实例

使用默认值创建,也可指定initialCapacity、loadFactor。 image.png 也可基于一个Map实例来创建。 image.png

2.2 put方法

  • WeakHashMap中允许key和value为null,key == null时被将使用常量Object NULL_KEY
  • 当存在哈希冲突时,将冲突项构建成链表,put新key时插入到链表头;
  • put一个新key后,可能触发扩容;
  • 每次扩容时,哈希表length增加1倍。 image.png

2.3 get方法

实现很简单。 image.png

2.4 删除失效的键

  • 当键不再被使用时(没有任何强引用),将随时被垃圾回收;
  • 键被gc回收后,它的entry会被放入ReferenceQueue<Object> queue中;
  • expungeStaleEntries方法中,循环调用queue.poll()拿到失效的entry,并将它从哈希表中移除;
  • WeakHashMap的get、put、remove、containsValue、forEach、resize等方法,都会触发清理失效的entry。 image.png

WeakHashMap自动清理失效的项,依赖于WeakReference、ReferenceQueue。 image.png

2.5 remove方法

主要逻辑与expungeStaleEntries()相似,只是

  • remove()由用户程序主动调用,并指定删除的key;
  • expungeStaleEntries()只能在调用WeakHashMap的某些方法时,被动触发。

3 总结

3.1 WeakHashMap的特点

  • 能自动释放不再使用的key所占内存,提高应用程序性能;
  • 由于需要频繁移除失效的键,在性能上会比HashMap略差;
  • 当哈希冲突严重时,单链表下的get(),性能不及HashMap中的红黑树;
  • 它是线程不安全的,应防止并发修改;
  • 在一些场景下,使用它来避免发生内存泄漏。

3.2 应用场景

WeakHashMap通常用于需要动态管理大量对象的应用中,可以避免内存泄漏问题:

  1. 缓存系统:使用WeakHashMap作为缓存的实现,entry项会在key不再被强引用时自动移除,避免浪费内存;
  2. 生命周期管理:如数据库连接、HTTP连接池、线程池等,使用WeakHashMap来保存对象,避免忘记关闭资源而造成的内存泄漏。