WeakHashMap总结
前言:WeakHashMap可能平时使用的频率并不高,但是你可能听过WeakHashMap会进行自动回收吧,下面就对其原理进行分析。
弱引用参考:
https://blog.csdn.net/qq_23412557/article/details/122486852
注:本文jdk源码版本为jdk1.8.0_172
1.WeakHashMap介绍
WeakHashMap是一种弱引用的map,底层数据结构为数组+链表,内部的key存储为弱引用,在GC时如果key不存在强引用的情况下会被回收掉,而对于value的回收会在下一次操作map时回收掉,所以WeakHashMap适合缓存处理。
1 java.lang.Object
2 ↳ java.util.AbstractMap<K, V>
3 ↳ java.util.WeakHashMap<K, V>
4
5 public class WeakHashMap<K,V>
6 extends AbstractMap<K,V>
7 implements Map<K,V> {}
从WeakHashMap的继承关系上来看,可知其继承AbstractMap,实现了Map接口。其底层数据结构是Entry数组,Entry的数据结构如下:
从源码上可知,Entry的内部并没有存储key的值,而是通过调用父类的构造方法,传入key和ReferenceQueue,最终key和queue会关联到Reference中,这里是GC时,清清除key的关键,这里大致看下Reference的源码:
1 private static class ReferenceHandler extends Thread {2 3 private static void ensureClassInitialized(Class<?> clazz) {4 try {5 Class.forName(clazz.getName(), true, clazz.getClassLoader());6 } catch (ClassNotFoundException e) {7 throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);8 }9 }10 11 static {12 // pre-load and initialize InterruptedException and Cleaner classes13 // so that we don't get into trouble later in the run loop if there's14 // memory shortage while loading/initializing them lazily.15 ensureClassInitialized(InterruptedException.class);16 ensureClassInitialized(Cleaner.class);17 }18 19 ReferenceHandler(ThreadGroup g, String name) {20 super(g, name);21 }22 23 public void run() {24 // 注意这里为一个死循环25 while (true) {26 tryHandlePending(true);27 }28 }29 }30 static boolean tryHandlePending(boolean waitForNotify) {31 Reference<Object> r;32 Cleaner c;33 try {34 synchronized (lock) {35 if (pending != null) {36 r = pending;37 // 'instanceof' might throw OutOfMemoryError sometimes38 // so do this before un-linking 'r' from the 'pending' chain...39 c = r instanceof Cleaner ? (Cleaner) r : null;40 // unlink 'r' from 'pending' chain41 pending = r.discovered;42 r.discovered = null;43 } else {44 // The waiting on the lock may cause an OutOfMemoryError45 // because it may try to allocate exception objects.46 if (waitForNotify) {47 lock.wait();48 }49 // retry if waited50 return waitForNotify;51 }52 }53 } catch (OutOfMemoryError x) {54 // Give other threads CPU time so they hopefully drop some live references55 // and GC reclaims some space.56 // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above57 // persistently throws OOME for some time...58 Thread.yield();59 // retry60 return true;61 } catch (InterruptedException x) {62 // retry63 return true;64 }65 66 // Fast path for cleaners67 if (c != null) {68 c.clean();69 return true;70 }71 // 加入对列72 ReferenceQueue<? super Object> q = r.queue;73 if (q != ReferenceQueue.NULL) q.enqueue(r);74 return true;75 }76 77 static {78 ThreadGroup tg = Thread.currentThread().getThreadGroup();79 for (ThreadGroup tgn = tg;80 tgn != null;81 tg = tgn, tgn = tg.getParent());82 // 创建handler83 Thread handler = new ReferenceHandler(tg, "Reference Handler");84 /* If there were a special system-only priority greater than85 * MAX_PRIORITY, it would be used here86 */87 // 线程优先级最大 88 handler.setPriority(Thread.MAX_PRIORITY);89 // 设置为守护线程90 handler.setDaemon(true);91 handler.start();92 93 // provide access in SharedSecrets94 SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {95 @Override96 public boolean tryHandlePendingReference() {97 return tryHandlePending(false);98 }99 });
100 }
通过查看Reference源码可知,在实例化时会创建一个守护线程,然后不断循环将GC时的Entry入队,关于如何清除value值的下面会进行分析。
2.具体源码分析
put操作:
1 public V put(K key, V value) {2 // 确定key值,允许key为null3 Object k = maskNull(key);4 // 获取器hash值5 int h = hash(k);6 // 获取tab7 Entry<K,V>[] tab = getTable();8 // 确定在tab中的位置 简单的&操作9 int i = indexFor(h, tab.length);
10 // 遍历,是否要进行覆盖操作
11 for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
12 if (h == e.hash && eq(k, e.get())) {
13 V oldValue = e.value;
14 if (value != oldValue)
15 e.value = value;
16 return oldValue;
17 }
18 }
19
20 // 修改次数自增
21 modCount++;
22 // 取出i上的元素
23 Entry<K,V> e = tab[i];
24 // 构建链表,新元素在链表头
25 tab[i] = new Entry<>(k, value, queue, h, e);
26 // 检查是否需要扩容
27 if (++size >= threshold)
28 resize(tab.length * 2);
29 return null;
30 }
分析:
WeakHashMap的put操作与HashMap相似,都会进行覆盖操作(相同key),但是注意插入新节点是放在链表头。上述代码中还要一个关键的函数getTable,后面会对其进行具体分析,先记下。
get操作:
1 public V get(Object key) {2 // 确定key3 Object k = maskNull(key);4 // 计算其hashCode5 int h = hash(k);6 Entry<K,V>[] tab = getTable();7 int index = indexFor(h, tab.length);8 // 获取对应位置上的元素9 Entry<K,V> e = tab[index];
10 while (e != null) {
11 // 如果hashCode相同,并且key也相同,则返回,否则继续循环
12 if (e.hash == h && eq(k, e.get()))
13 return e.value;
14 e = e.next;
15 }
16 // 未找到,则返回null
17 return null;
18 }
分析:
get操作逻辑简单,根据key遍历对应元素即可。
remove操作:
1 public V remove(Object key) {2 Object k = maskNull(key);3 int h = hash(k);4 Entry<K,V>[] tab = getTable();5 int i = indexFor(h, tab.length);6 // 数组上第一个元素7 Entry<K,V> prev = tab[i];8 Entry<K,V> e = prev;9 // 循环
10 while (e != null) {
11 Entry<K,V> next = e.next;
12 // 如果hash值相同,并且key一样,则进行移除操作
13 if (h == e.hash && eq(k, e.get())) {
14 // 修改次数自增
15 modCount++;
16 // 元素个数自减
17 size--;
18 // 如果就是头元素,则直接移除即可
19 if (prev == e)
20 tab[i] = next;
21 else
22 // 否则将前驱元素的next赋值为next,则将e移除
23 prev.next = next;
24 return e.value;
25 }
26 // 更新prev和e,继续循环
27 prev = e;
28 e = next;
29 }
30 return null;
31 }
分析:
移除元素操作的整体逻辑并不复杂,就是进行链表的常规操作,注意元素是链表头时的特别处理,通过上述注释,理解应该不困难。
resize操作
(WeakHashMap的扩容操作)
1 void resize(int newCapacity) {2 Entry<K,V>[] oldTable = getTable();3 // 原数组长度4 int oldCapacity = oldTable.length;5 if (oldCapacity == MAXIMUM_CAPACITY) {6 threshold = Integer.MAX_VALUE;7 return;8 }9 // 创建新的数组
10 Entry<K,V>[] newTable = newTable(newCapacity);
11 // 数据转移
12 transfer(oldTable, newTable);
13 table = newTable;
14
15 /*
16 * If ignoring null elements and processing ref queue caused massive
17 * shrinkage, then restore old table. This should be rare, but avoids
18 * unbounded expansion of garbage-filled tables.
19 */
20 // 确定扩容阈值
21 if (size >= threshold / 2) {
22 threshold = (int)(newCapacity * loadFactor);
23 } else {
24 // 清除被GC的value
25 expungeStaleEntries();
26 // 数组转移
27 transfer(newTable, oldTable);
28 table = oldTable;
29 }
30 }
31
32 private void transfer(Entry<K,V>[] src, Entry<K,V>[] dest) {
33 // 遍历原数组
34 for (int j = 0; j < src.length; ++j) {
35 // 取出元素
36 Entry<K,V> e = src[j];
37 src[j] = null;
38 // 链式找元素
39 while (e != null) {
40 Entry<K,V> next = e.next;
41 Object key = e.get();
42 // key被回收的情况
43 if (key == null) {
44 e.next = null; // Help GC
45 e.value = null; // " "
46 size--;
47 } else {
48 // 确定在新数组的位置
49 int i = indexFor(e.hash, dest.length);
50 // 插入元素 注意这里为头插法,会倒序
51 e.next = dest[i];
52 dest[i] = e;
53 }
54 e = next;
55 }
56 }
57 }
分析:
WeakHashMap的扩容函数中有点特别,因为key可能被GC掉,所以在扩容时也许要考虑这种情况,其他并没有什么特别的,通过以上注释理解应该不难。
过期元素(弱引用)清除
在以上源码分析中多次出现一个函数:expungeStaleEntries
1 private void expungeStaleEntries() {2 // 从队列中取出被GC的Entry3 for (Object x; (x = queue.poll()) != null; ) {4 synchronized (queue) {5 @SuppressWarnings("unchecked")6 Entry<K,V> e = (Entry<K,V>) x;7 // 确定元素在队列中的位置8 int i = indexFor(e.hash, table.length);9 // 取出数组中的第一个元素 prev
10 Entry<K,V> prev = table[i];
11 Entry<K,V> p = prev;
12 // 循环
13 while (p != null) {
14 Entry<K,V> next = p.next;
15 // 找到
16 if (p == e) {
17 // 判断是否是链表头元素 第一次时
18 if (prev == e)
19 // 将next直接挂在i位置
20 table[i] = next;
21 else
22 // 进行截断
23 prev.next = next;
24 // Must not null out e.next;
25 // stale entries may be in use by a HashIterator
26 e.value = null; // Help GC
27 size--;
28 break;
29 }
30 // 更新prev和p
31 prev = p;
32 p = next;
33 }
34 }
35 }
36 }
分析:
该函数的主要作用就是清除Entry的value,该Entry是在GC清除key的过程中入队的。函数的逻辑并不复杂,通过上述注释理解应该不难。
接下来看下该函数会在什么时候调用:
从以上调用链可知,在获取size(获取WeakHashMap的元素个数)和resize(扩容时)会调用该函数清除被GC的key对应的value值。但还有一个getTable函数也会调用该函数:
从以上调用链可知,在get/put等操作中都会调用expungeStaleEntries函数进GC后的收尾工作,其实WeakHashMap清除无强引用的核心也就是该函数了,因此理解该函数的作用是非常重要的。
3.总结
#1.WeakHashMap非同步,默认容量为16,扩容因子默认为0.75,底层数据结构为Entry数组(数组+链表)。
#2.WeakHashMap中的弱引用key会在下一次GC被清除,注意只会清除key,value会在每次map操作中清除。
WeakHashMap总结相关推荐
- java weakhashmap用法_Java WeakHashMap指南
概述 在这篇文章中,我们将探索 java.util 包中的WeakHashMap. 为了理解数据结构,我们将在这里使用它来推出一个简单的缓存实现.但是,请记住,这是为了了解地图的工作原理,并且创建自己 ...
- java 弱引用 集合_java 弱引用集合类WeakHashMap
java 弱引用集合类WeakHashMap Java集合框架中的WeakHashMap类是Map接口的一种特殊实现.它实现了Map接口,继承了AbstractMap抽象类.它实现了对key的弱引用. ...
- WeakHashMap和四种引用总结:
WeakHashMap 当一个键对象被GC(垃圾回收)回收时,响应的值对象的引用从map中删除,WeakHashMap能节约存储空间,来实现缓存那些非必要的数据 四种类型的引用: 强运用(Srtron ...
- 045_引用分类和WeakHashMap
一. 引用分类 1. 强引用: StrongReference, 引用指向对象, gc(Garbage collection)运行时不回收对象. 2. 软引用: SoftReference, gc(G ...
- LinkedHashMap 实现缓存(LRU、FIFO、weakhashMap)
原文地址:https://www.jianshu.com/p/c627a30cf14a 在文中使用了LinkedHashMap实现了LRU和FIFO缓存机制.实际上在LinkedHashMap的设计上 ...
- 浅谈WeakHashMap
Java WeakHashMap 到底Weak在哪里,它真的很弱吗?WeakHashMap 的适用场景是什么,使用时需要注意些什么?弱引用和强引用对Java GC有什么不同影响?本文将给出清晰而简洁的 ...
- Java集合框架:WeakHashMap
欢迎支持笔者新作:<深入理解Kafka:核心设计与实践原理>和<RabbitMQ实战指南>,同时欢迎关注笔者的微信公众号:朱小厮的博客. 欢迎跳转到本文的原文链接:https: ...
- WeakHashMap垃圾回收原理
介绍 WeakHashMap自然联想到的是HashMap.确实,WeakHashMap与HashMap一样是个散列表,存储内容也是键值对.与HashMap类似的功能就不展开了,本文重点关注在WeakH ...
- WeakHashMap和Java引用类型详细解析
WeakHashMap是种弱引用的HashMap,这是说,WeakHashMap里的key值如果没有外部强引用,在垃圾回收之后,WeakHashMap的对应内容也会被移除掉. 1.1 Java的引用类 ...
- weakhashmap_Java WeakHashMap values()方法与示例
weakhashmap WeakHashMap类values()方法 (WeakHashMap Class values() method) values() method is available ...
最新文章
- 节约能源,做个合格的环保主义者,不要做网络灾民
- 找不到或无法加载主类 org.jivesoftware.openfire.starter.ServerStarter
- 查找文本字母并且统计个数
- Android监听button按钮的click事件
- 最小二乘法原理及极值点判定
- C# 两时间,时间间隔
- JMetro“ Metro”选项卡,Java的TreeView和ContextMenu(JavaFX)
- 微型计算机控制技术试卷B,微型计算机控制技术试卷b..doc
- JavaScript操作XML(IE6下)
- unity案例 mysql lua_通过Xlua实现unity热更新的一个小例子
- win10,Net Share 共享,每次开机都会出现 --- 彻底关闭的方法---禁用server服务
- 平滑线反锯齿工具_Photoshop中的华丽渐变工具的使用
- Domino中运用ajax判断帐号是否存在的简单例子
- Optisystem7中 matlab元件 使用
- html页面小宠物代码大全,宠物店网页设计html代码
- 51单片机读引脚和读端口测试总结
- 楼梯计算机公式,楼梯的计算公式 楼梯的尺寸
- [html5游戏开发]数独游戏-完整算法-开源讲座
- Hyper-V自定义专用网络网段
- 科利转债上市价格预测
热门文章
- tf1.0的代码放到tf2.0上
- 苏轼与江西交通的不解之缘
- 可信执行环境(TEE)介绍
- ASEMI代理艾赛斯二极管DSA300I100NA,肖特基DSA300I100NA
- NullPointerException:method 'android.content.BroadcastReceiver.onReceive' on a null object reference
- 财富时报:错过社交网站就错过未来
- [Linux] PHP程序员玩转Linux系列-升级PHP到PHP7
- 2021-2025年中国运动水壶行业市场供需与战略研究报告
- Axure9发布到局域网web服务器教程
- 309 复制单级文件夹