JDK 8 WeakHashMap 详解
WeakHashMap是 Java 中一种用于存储键值对的Map类型。它与常规HashMap的区别在于处理键的方式。在WeakHashMap中,键是被 “弱引用”(WeakReference) 持有。这意味着当键对象在程序的其他地方不再被引用(即它符合垃圾回收条件)时,WeakHashMap中相应的键值对会自动被移除。这种行为基于 Java 中的弱引用概念。弱引用允许对象在没有强引用指向它们时被垃圾回收。WeakHashMap利用这个机制来管理其条目, 这种特性使它非常适合实现缓存或映射需要动态释放资源的场景。以下是其核心原理和源码分析。
一、核心概念
1. 弱引用机制
- 弱引用(WeakReference):当垃圾回收器扫描到弱引用对象时,无论内存是否充足,都会回收该对象,并将其引用队列(
ReferenceQueue)中。 - WeakHashMap 原理:键对象被包装成弱引用,当键对象被 GC 回收后,对应的键值对会在下一次操作(如
get、size)时被清理。
2. 数据结构
- 内部结构:与
HashMap类似,使用数组 + 链表实现哈希表。 - 键的存储:键被包装为
WeakReference,值被普通引用存储。
二、源码解析
1. 类定义与成员变量
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> {
// 默认初始容量
private static final int DEFAULT_INITIAL_CAPACITY = 16;
// 最大容量
private static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认负载因子
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 存储键值对的数组(每个元素是一个链表头节点)
private Entry<K,V>[] table;
// 键值对数量
private int size;
// 扩容阈值(容量 * 负载因子)
private int threshold;
// 负载因子
private final float loadFactor;
// 引用队列,用于存储被 GC 回收的键的引用
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
// 修改次数,用于快速失败机制
int modCount;
// 内部 Entry 类,继承自 WeakReference
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final int hash;
Entry<K,V> next;
Entry(Object key, V value, ReferenceQueue<Object> queue, int hash, Entry<K,V> next) {
super(key, queue); // 将 key 包装为弱引用,并关联引用队列
this.value = value;
this.hash = hash;
this.next = next;
}
// 其他方法...
}
}
2. 核心方法:put
public V put(K key, V value) {
Object k = maskNull(key); // 处理 null 键(转换为内部的 NULL_KEY 对象)
int h = hash(k); // 计算哈希值
Entry<K,V>[] tab = getTable(); // 获取当前 table(可能已清理过期条目)
int i = indexFor(h, tab.length); // 计算数组索引
// 遍历链表查找是否已存在该键
for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
if (h == e.hash && eq(k, e.get())) {
V oldValue = e.value;
if (value != oldValue)
e.value = value;
return oldValue;
}
}
modCount++;
Entry<K,V> e = tab[i];
tab[i] = new Entry<>(k, value, queue, h, e); // 创建新 Entry 并插入链表头部
if (++size >= threshold)
resize(tab.length * 2); // 扩容
return null;
}
3. 核心方法:get
public V get(Object key) {
Object k = maskNull(key);
int h = hash(k);
Entry<K,V>[] tab = getTable();
int index = indexFor(h, tab.length);
Entry<K,V> e = tab[index];
// 遍历链表查找键值对
while (e != null) {
if (e.hash == h && eq(k, e.get()))
return e.value;
e = e.next;
}
return null;
}
4. 清理过期条目
// 获取 table 前先清理已被 GC 的键对应的条目
private Entry<K,V>[] getTable() {
expungeStaleEntries();
return table;
}
// 清理已被 GC 的键对应的条目
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;
// 帮助 GC
e.value = null; // 清除对值的引用
size--;
break;
}
prev = p;
p = next;
}
}
}
}
5. 扩容机制
// 扩容方法,与 HashMap 类似
void resize(int newCapacity) {
Entry<K,V>[] oldTable = getTable();
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry<K,V>[] newTable = new Entry[newCapacity];
boolean oldAltHashing = useAltHashing;
useAltHashing |= sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = oldAltHashing ^ useAltHashing;
transfer(oldTable, newTable, rehash);
table = newTable;
// 调整阈值
if (size >= threshold / 2) {
threshold = (int)(newCapacity * loadFactor);
} else {
// 清理过程中可能移除了大量条目,重新计算阈值
expungeStaleEntries();
transfer(newTable, oldTable, false);
table = oldTable;
}
}
三、关键特性分析
1. 弱引用与自动清理
- 自动回收:当键对象被 GC 回收后,对应的
WeakReference会被放入引用队列,WeakHashMap在后续操作中会清理这些条目。 - 触发时机:
expungeStaleEntries()方法在getTable()、size()、resize()等方法中被调用,确保过期条目被及时清理。
2. null 键支持
WeakHashMap允许 null 键,内部将其转换为特殊的NULL_KEY对象。private static final Object NULL_KEY = new Object(); private static Object maskNull(Object key) { return (key == null) ? NULL_KEY : key; }
3. 线程不安全
- 与
HashMap类似,WeakHashMap非线程安全。多线程环境下需使用Collections.synchronizedMap包装。Map<K, V> synchronizedMap = Collections.synchronizedMap(new WeakHashMap<>());
4. 应用场景
- 缓存:实现内存敏感的缓存,当内存不足时自动释放资源。
- 元数据关联:为对象关联临时元数据,当对象被回收时,关联数据自动清除。
四、使用场景
-
缓存实现
// 缓存大对象,当对象无其他强引用时自动释放 Map<BigObject, Metadata> cache = new WeakHashMap<>(); // 添加缓存 cache.put(bigObject, new Metadata("info")); // 使用缓存 Metadata metadata = cache.get(bigObject); -
对象关联数据
// 为对象关联临时状态,对象被回收时状态自动清除 Map<MyObject, State> states = new WeakHashMap<>(); public void setObjectState(MyObject obj, State state) { states.put(obj, state); } -
防止内存泄漏
// 监听器注册(避免持有对象强引用导致内存泄漏) Map<Listener, Object> listeners = new WeakHashMap<>(); public void registerListener(Listener listener) { listeners.put(listener, new Object()); }
五、注意事项
-
值的强引用:
- 值对象通过强引用存储,若值对象持有对键的强引用,会阻止键被 GC 回收。
-
性能考虑:
- 频繁 GC 可能导致
WeakHashMap频繁清理,影响性能。
- 频繁 GC 可能导致
-
遍历行为:
- 迭代过程中可能会发现条目突然消失,因为它们可能在迭代期间被 GC 回收。
-
替代方案:
- Java 8 引入的
ConcurrentHashMap+WeakReference可实现线程安全的弱引用缓存。 - Guava 库的
CacheBuilder提供更灵活的缓存实现。
- Java 8 引入的
六、总结
JDK 8 的 WeakHashMap 通过弱引用机制实现了键的自动回收,非常适合需要动态释放资源的场景。其核心在于利用 WeakReference 和 ReferenceQueue 跟踪被 GC 回收的键,并在后续操作中清理对应的条目。理解其弱引用特性、自动清理机制和线程不安全的特点,有助于在实际开发中正确应用 WeakHashMap 解决内存敏感的问题。
WeakHashMap 是 Java 中一个强大且独特的数据结构,通过弱引用管理键值对,在内存敏感的场景如缓存实现、内存管理等方面发挥着重要作用。深入理解其原理、使用方法以及源码实现,有助于我们在编写 Java 程序时更好地优化内存使用,提升程序的性能和稳定性。在实际应用中,遵循最佳实践,合理选择键并处理好多线程并发问题,能让 WeakHashMap 发挥出最大的价值。
634

被折叠的 条评论
为什么被折叠?



