JDK源码分析(9)之 WeakHashMap 相关
平时我们使用最多的数据结构肯定是 HashMap,但是在使用的时候我们必须知道每个键值对的生命周期,并且手动清除它;但是如果我们不是很清楚它的生命周期,这时候就比较麻烦;通常有这样几种处理方式:
- 由一个线程定时处理,可以是
Timer
或者ScheduledThreadPoolExecutor
; - 利用重写
LinkedHashMap.removeEldestEntry()
,实现 FIFOCache 或者 LRUCache;可以参考我之前写的一篇博客 LinkedHashMap 相关; - 利用
WeakHashMap
的特性,如果逻辑比较复杂还可以直接使用Reference
;这里可以参考 Reference 完全解读 和 Reference 框架概览;
所以本文将主要介绍WeakHashMap
的特性,以及补充一些关于 HashMap 实现的对比;相关 HashMap 的介绍也可以参考 HashMap 相关;
一、使用场景
上面也介绍了,WeakHashMap
适用于不是非常重要的缓存类似的场景;例如:
WeakHashMap<Object, Integer> map = new WeakHashMap<>();
for (int i = 0; i < 100; i++) {
map.put(new Object(), i);
}
System.out.println(map.size()); // 1
System.gc(); // 2
System.out.println(map.size()); // 3
System.out.println(map.size()); // 4
System.out.println(map.size()); // 5
System.out.println(map); // 6
System.out.println(map.size()); // 7
// 打印:
100
100
100
46
{}
0
对于以上的结果你可能和我打印的不一样,WeakHashMap
按照语义应该是,当 key 没有强引用指向的时候,会自动清除 key 和 value;我这里先解释它的释放过程,如果你觉得很清晰,那WeakHashMap
你就算是掌握了;
- 首先 for 循环结束的时候,key 已经没用强引用指向了,此时所有的 key 都是弱引用了;
- 接下来执行1,因为我这里只有一个方法,新生代还有足够的空间,所以不会触发 GC,所以所有的 key 任然在堆里面,所以打印100;
- 然后手动触发 GC,虽然
System.gc();
不一定会立即执行,但是我这里只有一个方法,所以肯定会执行 GC,这里可以打开 GC 日志查看,-verbose:gc
;因为 所有的 key 都是弱引用,所以referent
被致为 null,同时将 key 注册到ReferenceQueue
中; - 在执行 3-7 的时候,按语义 map 应该为空;但是将 key 注册到
ReferenceQueue
并非原子性一次完成的,所以这里会打印不同的值,每注册完成一个,在 map 进行操作的时候,就会将其移除;
将上面的代码改成多线程分析思路也是一样的,如果你觉得有不清楚的地方可以查看下文;
二、WeakHashMap 源码分析
1. 类定义
public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>
可以看到虽然WeakHashMap
也是基于哈希表,但是却并非像LinkedHashMap
一样是继承于HashMap
,并且WeakHashMap
也没有实现Cloneable, Serializable
两个接口,这是因为WeakHashMap
基于WeakReference
实现的,弱引用并不建议实现序列化,同时弱引用一般用于不是很重要的缓存,也就没必要实现Cloneable, Serializable
两个接口了;
2. 核心方法
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
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);
this.value = value;
this.hash = hash;
this.next = next;
}
public K getKey() { }
public V getValue() {
public V setValue(V newValue) {
public int hashCode() {
public String toString() {
}
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;
}
}
}
}
上面代码所列的ReferenceQueue,Entry,expungeStaleEntries()
就是WeakHashMap
实现的核心了;这里强烈建议要先看 Reference 完全解读 和 Reference 框架概览 这两篇博客,里面同样的内容我也不会再赘述了;
Entry<K,V> extends WeakReference<Object>
, 表明所有的节点都是WeakReference
,而 key 则是 referent;- queue,所有 key 使用同一个
ReferenceQueue
监听器,每当 key 被回收的时候,entry 将会被注册到ReferenceQueue
中; - expungeStaleEntries,将注册到
ReferenceQueue
中的 entry 移除,并将 value 置为 null;WeakHashMap
的所有操作都先执行expungeStaleEntries
,这样WeakHashMap
就实现了自动回收不在需要的 key 和 value;
三、性能对比
其实上面的内容就已经将WeakHashMap
的主要实现讲完了,但是我之前在看HashMap
源码的时候,并没有对比 JDK1.7 和 JDK1.8,但是在这里发现其实WeakHashMap
的实现和 JDK1.7 差不多,所以接下来我将简单对比一下WeakHashMap
和HashMap
;
1. 容量计算
在WeakHashMap
和HashMap
中都要求容量是2的幂,因为当容量为2的幂时,使用除留余数法计算哈希桶位置时可以使用hash % length = hash & (length-1)
的性质进行优化;
// WeakHashMap
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
// HashMap
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
简单测试可以得到:
initCap = 10 | 50 | 100 | |
---|---|---|---|
WeakHashMap | 30 | 32 | 26 |
HashMap | 3 | 3 | 3 |
代码比较简单我就不贴了,从上表也可以看到了tableSizeFor
不仅高效而且稳定;
2. 哈希计算
// WeakHashMap
final int hash(Object k) {
int h = k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
// HashMap
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
两种hash算法都是要避免极端的hashCode()
,但是HashMap
却更为透彻,因为影响哈希桶位置的只有 hash 的低位(容量2的n次方,n个低位),直接将高位与上低位,使高位 hash 参与位置计算,简洁且高效;
此外还有put
方法,但是里面还牵涉红黑树,对于本文就扯得有点远了,所以暂不讲;
总结
WeakHashMap
是WeakReference
的典型应用,在灵活应用WeakHashMap
之后,如果有更为复杂的逻辑,可以直接使用Reference
实现;- 另外
WeakHashMap
的自动回收机制是操作时检查,所以WeakHashMap
里面即使有可回收对象,但是很久都没有操作也是没法及时清理,所以在使用的时候,需要经常对它操作一下,才能及时回收垃圾;
JDK源码分析(9)之 WeakHashMap 相关的更多相关文章
- JDK源码分析(三)—— LinkedList
参考文档 JDK源码分析(4)之 LinkedList 相关
- JDK源码分析(一)—— String
dir 参考文档 JDK源码分析(1)之 String 相关
- 【JDK】JDK源码分析-HashMap(1)
概述 HashMap 是 Java 开发中最常用的容器类之一,也是面试的常客.它其实就是前文「数据结构与算法笔记(二)」中「散列表」的实现,处理散列冲突用的是“链表法”,并且在 JDK 1.8 做了优 ...
- 【JDK】JDK源码分析-TreeMap(2)
前文「JDK源码分析-TreeMap(1)」分析了 TreeMap 的一些方法,本文分析其中的增删方法.这也是红黑树插入和删除节点的操作,由于相对复杂,因此单独进行分析. 插入操作 该操作其实就是红黑 ...
- 【JDK】JDK源码分析-Vector
概述 上文「JDK源码分析-ArrayList」主要分析了 ArrayList 的实现原理.本文分析 List 接口的另一个实现类:Vector. Vector 的内部实现与 ArrayList 类似 ...
- 【JDK】JDK源码分析-List, Iterator, ListIterator
List 是最常用的容器之一.之前提到过,分析源码时,优先分析接口的源码,因此这里先从 List 接口分析.List 方法列表如下: 由于上文「JDK源码分析-Collection」已对 Collec ...
- 【JDK】JDK源码分析-AbstractQueuedSynchronizer(2)
概述 前文「JDK源码分析-AbstractQueuedSynchronizer(1)」初步分析了 AQS,其中提到了 Node 节点的「独占模式」和「共享模式」,其实 AQS 也主要是围绕对这两种模 ...
- 【JDK】JDK源码分析-AbstractQueuedSynchronizer(3)
概述 前文「JDK源码分析-AbstractQueuedSynchronizer(2)」分析了 AQS 在独占模式下获取资源的流程,本文分析共享模式下的相关操作. 其实二者的操作大部分是类似的,理解了 ...
- 【JDK】JDK源码分析-ReentrantLock
概述 在 JDK 1.5 以前,锁的实现只能用 synchronized 关键字:1.5 开始提供了 ReentrantLock,它是 API 层面的锁.先看下 ReentrantLock 的类签名以 ...
- 【JDK】JDK源码分析-CountDownLatch
概述 CountDownLatch 是并发包中的一个工具类,它的典型应用场景为:一个线程等待几个线程执行,待这几个线程结束后,该线程再继续执行. 简单起见,可以把它理解为一个倒数的计数器:初始值为线程 ...
随机推荐
- Markdown常用快捷键
Markdown使用的符号:井号,星号,大于号,中括号,竖线,横杠,波浪线,反引号 # ,*, > ,[],|,-,~,` 井号 + 空格:根据空格的个数显示各标题的大小 标题一 标题二 标题三 ...
- HBase shell scan 过滤器用法总结
比较器: 前面例子中的regexstring:2014-11-08.*.binary:\x00\x00\x00\x05,这都是比较器.HBase的filter有四种比较器: (1)二进制比较器:如’b ...
- esxi 精简置备只增不减问题解决方法(转)
esxi 精简置备只增不减问题解决方法 众所周知Thin Provisioning模式下的虚拟机磁盘的空间会随需增长,可以很大程度上帮助我们节约空间,可是,凡增长过后的空间,即使清除了导致增长的文件后 ...
- DOM-节点概念-属性
1.节点的概念 页面中的所有内容,包括标签,属性,文本(文字,空格,回车,换行等),也就是说页面的所有内容都可以叫做节点. 2.节点相关的属性 2.1.节点分类 **标签节点:**比如 div 标签, ...
- (一) sublime安装和使用
1 下载安装sublime 可以破解也可以不破解 2 html基础架子自动生成插件Emmet的安装 3 Emmet 安装失败解决 4 快捷键设置和汇总 4 其他sublime插件汇总
- 错误:Java HotSpot(TM) 64-Bit Server VM warning: Insufficient space for shared memory file
Java HotSpot(TM) 64-Bit Server VM warning: Insufficient space for shared memory file: /tmp/hsperfdat ...
- socket 套接字服务器端和客户端发送信息
import socket import threading host='' port=6889 def cilenThred(conn,addr): print("成功接受客户端{}的连接 ...
- Python基础之模块+异常
一.模块相关概念 1.定义:包含一系列数据.函数.类的文件,通常以.py结尾. 2.作用:让一些相关的数据,函数,类有逻辑的组织在一起,使逻辑结构更加清晰.有利于多人合作开发. 3.模块导入方式(三种 ...
- Naïve Media Player, Part 2
项目主页网址: https://github.com/Judylalala/en Q1:如何播放online歌曲? A1:我本来以为会为MediaElement设置一个单独的属性播放online音频. ...
- QEMU KVM Libvirt手册(11): Managing Storage
When managing a VM Guest on the VM Host Server itself, it is possible to access the complete file sy ...