Java WeakHashMap 源码解析
前面把基于特定数据结构的Map介绍完了,它们分别利用了相应数据结构的特点来实现特殊的目的,像HashMap利用哈希表的快速插入、查找实现O(1)
的增删改查,TreeMap则利用了红黑树来保证key的有序性的同时,使得增删改查的时间复杂度为O(log(n))
。
今天要介绍的WeakHashMap并没有基于某种特殊的数据结构,它的主要目的是为了优化JVM,使JVM中的垃圾回收器(garbage collector,后面简写为 GC)更智能的回收“无用”的对象。
引用类型
WeakHashMap
与其他 Map 最主要的不同之处在于其 key 是弱引用类型,其他 Map 的 key 均为强引用类型,说到这里,必须强调下:Java 中,引用有四种类型,分别为:强(strong)引用、软(soft)引用、弱(weak)引用、虚(phantom,本意为幽灵)引用。我相信对于 Java 初学者来说,不一定听过这几种引用类似,下面先介绍下这几种类型。
强引用
这是最常用的引用类型,在执行下面的语句时,变量 o
即为一个强引用。
Object o = new Object();
强引用指向的对象无论在何时,都不会被GC 清理掉。
一般来说,对于常驻类应用(比如server),随着时间的增加,所占用的内存往往会持续上升,如果程序中全部使用强引用,那么很容易造成内存泄漏,最终导致Out Of Memory (OOM)
,所以 Java 中提供了除强引用之外的其他三种引用,它们全部位于java.lang.ref
包中,下面一一介绍。
java.lang.ref.Reference
java.lang.ref.Reference
为 软(soft)引用、弱(weak)引用、虚(phantom)引用的父类。
Reference类继承关系
下面分析下Reference
的源码(其他三种引用都是其子类,区分不是很大)。
构造函数
//referent 为引用指向的对象 Reference(T referent) { this(referent, null); } //ReferenceQueue对象,可以简单理解为一个队列 //GC 在检测到appropriate reachability changes之后, //会把引用对象本身添加到这个queue中,便于清理引用对象本身 Reference(T referent, ReferenceQueue<? super T> queue) { this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue; }
如果我们在创建一个引用对象时,指定了ReferenceQueue
,那么当引用对象指向的对象达到合适的状态(根据引用类型不同而不同)时,GC 会把引用对象本身添加到这个队列中,方便我们处理它,因为
引用对象指向的对象 GC 会自动清理,但是引用对象本身也是对象(是对象就占用一定资源),所以需要我们自己清理。
举个例子:
SoftReference<String> ss = new SoftReference<String>("abc" , queue);
ss
为软引用,指向abc
这个对象,abc
会在一定时机被 GC 自动清理,但是ss
对象本身的清理工作依赖于queue
,当ss
出现在queue
中时,说明其指向的对象已经无效,可以放心清理ss
了。
从上面的分析大家应该对Reference
类有了基本的认识,但是上面也提到了,不同的引用,添加到ReferenceQueue
的时机是不一样。下面介绍具体引用时再进行说明。
这里有个问题,如果创建引用对象是没有指定ReferenceQueue
,引用对象会怎么样呢?这里需要了解Reference
类内部的四种状态。
四种状态
每一时刻,Reference
对象都处于下面四种状态中。这四种状态用Reference
的成员变量queue
与next
(类似于单链表中的next)来标示。
ReferenceQueue<? super T> queue; Reference next;
Active。新创建的引用对象都是这个状态,在 GC 检测到引用对象已经到达合适的reachability时,GC 会根据引用对象是否在创建时制定ReferenceQueue
参数进行状态转移,如果指定了,那么转移到Pending
,如果没指定,转移到Inactive
。在这个状态中
//如果构造参数中没指定queue,那么queue为ReferenceQueue.NULL,否则为构造参数中传递过来的queue queue = ReferenceQueue || ReferenceQueue.NULL next = null
Pending。pending-Reference列表中的引用都是这个状态,它们等着被内部线程ReferenceHandler
处理(会调用ReferenceQueue.enqueue
方法)。没有注册的实例不会进入这个状态。在这个状态中
//构造参数参数中传递过来的queue queue = ReferenceQueue next = 该queue中的下一个引用,如果是该队列中的最后一个,那么为this
Enqueued。调用ReferenceQueue.enqueued
方法后的引用处于这个状态中。没有注册的实例不会进入这个状态。在这个状态中
queue = ReferenceQueue.ENQUEUED next = 该queue中的下一个引用,如果是该队列中的最后一个,那么为this
Inactive。最终状态,处于这个状态的引用对象,状态不会在改变。在这个状态中
queue = ReferenceQueue.NULL next = this
有了这些约束,GC 只需要检测next
字段就可以知道是否需要对该引用对象采取特殊处理
- 如果
next
为null
,那么说明该引用为Active
状态 - 如果
next
不为null
,那么 GC 应该按其正常逻辑处理该引用。
我自己根据Reference.ReferenceHandler.run
与ReferenceQueue.enqueue
这两个方法,画出了这四种状态的转移图,供大家参考:
Reference状态转移图
要理解这个状态 GC 到底做了什么事,需要看 JVM 的代码,我这里时间、能力都不够,就不献丑了,后面有机会再来填坑。
对于一般程序员来说,这四种状态完全可以不用管。最后简单两句话总结上面的四种状态:
- 如果构造函数中指定了
ReferenceQueue
,那么事后程序员可以通过该队列清理引用 - 如果构造函数中没有指定了
ReferenceQueue
,那么 GC 会自动清理引用
get
调用Reference.get
方法可以得到该引用指向的对象,但是由于指向的对象随时可能被 GC 清理,所以即使在同一个线程中,不同时刻的调用可能返回不一样的值。
软引用(soft reference)
软引用“保存”对象的能力稍逊于强引用,但是高于弱引用,一般用来实现memory-sensitive caches。
软引用指向的对象会在程序即将触发
OOM
时被GC 清理掉,之后,引用对象会被放到ReferenceQueue
中。
弱引用(weak reference)
软引用“保存”对象的能力稍逊于弱引用,但是高于虚引用,一般用来实现canonicalizing mapping,也就是本文要讲的WeakHashMap
。
当弱引用指向的对象只能通过弱引用(没有强引用或弱引用)访问时,GC会清理掉该对象,之后,引用对象会被放到
ReferenceQueue
中。
虚引用(phantom reference)
虚引用是“保存”对象能力最弱的引用,一般用来实现scheduling pre-mortem cleanup actions in a more flexible way than is possible with the Java finalization mechanism
调用虚引用的
get
方法,总会返回null
,与软引用和弱引用不同的是,虚引用被enqueued
时,GC 并不会自动清理虚引用指向的对象,只有当指向该对象的所有虚引用全部被清理(enqueued后)后或其本身不可达时,该对象才会被清理。
WeakHashMap.Entry
上面介绍了很多引用的知识点,其实WeakHashMap
本身没什么好说的,只要是把引用的作用与使用场景搞清楚了,再来分析基于这些引用的对象就会很简单了。WeakHashMap
与HashMap
的签名与构造函数一样,这里就不介绍了,这里重点介绍下Entry
这个内部对象,因为其保存具体key-value对,所以把它弄清楚了,其他的就问题不大了。
/** * The entries in this hash table extend WeakReference, using its main ref * field as the key. */ private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> { V value; 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,说明key为弱引用(没有显式的 this.key = key) //所有如果key只有通过弱引用访问时,key会被 GC 清理掉 //同时该key所代表的Entry会进入queue中,等待被处理 //还可以看到value为强引用(有显式的 this.value = value ),但这并不影响 //后面可以看到WeakHashMap.expungeStaleEntries方法是如何清理value的 super(key, queue); this.value = value; this.hash = hash; this.next = next; } @SuppressWarnings("unchecked") //在获取key时需要unmaskNull,因为对于null的key,是用WeakHashMap的内部成员属性来表示的 public K getKey() { 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 ((k==null ? 0 : k.hashCode()) ^ (v==null ? 0 : v.hashCode())); } public String toString() { return getKey() + "=" + getValue(); } }
WeakHashMap.expungeStaleEntries
/** * Reference queue for cleared WeakEntries */ // 所有Entry在构造时都传入该queue private final ReferenceQueue<Object> queue = new ReferenceQueue<>(); /** * Expunges stale entries from the table. */ private void expungeStaleEntries() { for (Object x; (x = queue.poll()) != null; ) { synchronized (queue) { // e 为要清理的对象 @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 循环遍历冲突链 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 // 可以看到这里把value赋值为null,来帮助 GC 回收强引用的value e.value = null; // Help GC size--; break; } prev = p; p = next; } } } }
知道了expungeStaleEntries
方法的作用,下面看看它是何时被调用的
expungeStaleEntries调用链
可以看到,在对WeakHashMap
进行增删改查时,都调用了expungeStaleEntries
方法。
实战
上面说了,下面来个具体的例子帮助大家消化
import java.util.WeakHashMap; class KeyHolder { @Override protected void finalize() throws Throwable { System.out.println("I am over from key"); super.finalize(); } } class ValueHolder { @Override protected void finalize() throws Throwable { System.out.println("I am over from value"); super.finalize(); } } public class RefTest { public static void main(String[] args) { WeakHashMap<KeyHolder, ValueHolder> weakMap = new WeakHashMap<KeyHolder, ValueHolder>(); KeyHolder kh = new KeyHolder(); ValueHolder vh = new ValueHolder(); weakMap.put(kh, vh); while (true) { for (KeyHolder key : weakMap.keySet()) { System.out.println(key + " : " + weakMap.get(key)); } try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("here..."); //这里把kh设为null,这样一来就只有弱引用指向kh指向的对象 kh = null; System.gc(); } } }
输出
KeyHolder@a15670a : ValueHolder@20e1ed5b here... I am over from key //输出这句话说明,该key对应的Entry已经被 GC 清理 here... here... here... ... ... ...
总结
说实话,之前我是没怎么了解过引用,更是没有用过WeakHashMap
这个类,这次算是把这个坑给填上了。引用的使用场景应该是在常驻类或消耗内存较大应用中才用得上,我自己确实没怎么经历过这种类型的项目,只能现在打好基础,以后有机会在尝试。
其实关于引用,本文重点介绍了弱引用的使用场景,其他的没怎么介绍,感兴趣的可以阅读参考中给出的链接。
Java WeakHashMap 源码解析的更多相关文章
- 【转】Java HashMap 源码解析(好文章)
.fluid-width-video-wrapper { width: 100%; position: relative; padding: 0; } .fluid-width-video-wra ...
- Java集合类源码解析:Vector
[学习笔记]转载 Java集合类源码解析:Vector 引言 之前的文章我们学习了一个集合类 ArrayList,今天讲它的一个兄弟 Vector.为什么说是它兄弟呢?因为从容器的构造来说,Vec ...
- Java——LinkedHashMap源码解析
以下针对JDK 1.8版本中的LinkedHashMap进行分析. 对于HashMap的源码解析,可阅读Java--HashMap源码解析 概述 哈希表和链表基于Map接口的实现,其具有可预测的迭 ...
- java集合 源码解析 学习手册
学习路线: http://www.cnblogs.com/skywang12345/ 总结 1 总体框架 2 Collection架构 3 ArrayList详细介绍(源码解析)和使用示例 4 fai ...
- Java集合类源码解析:ArrayList
目录 前言 源码解析 基本成员变量 添加元素 查询元素 修改元素 删除元素 为什么用 "transient" 修饰数组变量 总结 前言 今天学习一个Java集合类使用最多的类 Ar ...
- Java集合类源码解析:AbstractMap
目录 引言 源码解析 抽象函数entrySet() 两个集合视图 操作方法 两个子类 参考: 引言 今天学习一个Java集合的一个抽象类 AbstractMap ,AbstractMap 是Map接口 ...
- Java集合类源码解析:LinkedHashMap
前言 今天继续学习关于Map家族的另一个类 LinkedHashMap .先说明一下,LinkedHashMap 是继承于 HashMap 的,所以本文只针对 LinkedHashMap 的特性学习, ...
- Java集合类源码解析:HashMap (基于JDK1.8)
目录 前言 HashMap的数据结构 深入源码 两个参数 成员变量 四个构造方法 插入数据的方法:put() 哈希函数:hash() 动态扩容:resize() 节点树化.红黑树的拆分 节点树化 红黑 ...
- Java - TreeMap源码解析 + 红黑树
Java提高篇(二七)-----TreeMap TreeMap的实现是红黑树算法的实现,所以要了解TreeMap就必须对红黑树有一定的了解,其实这篇博文的名字叫做:根据红黑树的算法来分析TreeMap ...
随机推荐
- colorAccent、colorPrimary、colorPrimaryDark actionbar toolbar navigationbar
伴随着Android5.0的发布也更新了support-v7-appcompat 到V21,其中增加了ToolBar.recyclerview.cardview等控件. Android5.0对改变AP ...
- jquery之获取当前时间
/** * * 获取当前时间 */ function p(s) { return s < 10 ? '0' + s: s; } var myDate = new Date(); //获取当前年 ...
- 在ssh框架中注解方式需要注意的几个问题
1.注解方式的时候 Spring 2.5 引入了 @Autowired 注释,它可以对类成员变量.方法及构造函数进行标注,完成自动装配的工作. 通过 @Autowired的使用来消除 set ,get ...
- Sublime Text 2 自动开启换行 Word Wrap
首先当然要夸一下神器 Sublime Text 2,自从第一次用我就彻底把神马 Notepad++ 和 TextMate 打入冷宫,用来开发 WEB 项目从此 IDE 都不需要了! 下面讲讲如何自动开 ...
- C#敏感关键词过滤代码
System.Text.StringBuilder sb = new System.Text.StringBuilder(text.Length); string filter ...
- iOS开发之字典数据建立模型步骤
1. 在控制器属性的(questions)set方法中完成字典转模型的操作 - (NSArray *)questions { if (nil == _questions) { //1.加载plist文 ...
- UICollectionView设置item(cell)之间间距为0(紧挨在一起的效果)
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; self.layout = layout ...
- 关于C#编程中引用与值类型赋值的一些容易犯错的地方
值类型与引用类型的区别在于:值类型在赋值的时候是拷贝值,引用类型在赋值的时候的拷贝引用.记住这一个原则,我们再来分析一些具体情况: PointStruct pt1 = ,); PointStruct ...
- web初识-tomcat的基本使用
Web入门 1. 软件结构: C/S : 有客户端, 维护较麻烦, 需要客户端和服务器端都更新. B/S : 优点 : 客户端就是浏览器, 更新方便, 只需要更新服务器端即可 ...
- MySQL存储过程带in和out参数
MySQL存储过程带in和out参数 最简单的例子: [html] mysql> DELIMITER $$ mysql> USE test $$ Database changed mysq ...