java weakhashmap 源码解析-mile米乐体育
前面把基于特定数据结构的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
的源码(其他三种引用都是其子类,区分不是很大)。 构造函数
//referent 为引用指向的对象 reference(t referent) { this(referent, null); } //referencequeue对象,可以简单理解为一个队列 //gc 在检测到appropriate reachability changes之后, //会把引用对象本身添加到这个queue中,便于清理引用对象本身 reference(t referent, referencequeue queue) { this.referent = referent; this.queue = (queue == null) ? referencequeue.null : queue; }
如果我们在创建一个引用对象时,指定了referencequeue
,那么当引用对象指向的对象达到合适的状态(根据引用类型不同而不同)时,gc 会把引用对象本身添加到这个队列中,方便我们处理它,因为
引用对象指向的对象 gc 会自动清理,但是引用对象本身也是对象(是对象就占用一定资源),所以需要我们自己清理。
举个例子:
softreferencess = new softreference ("abc" , queue);
ss
为软引用,指向abc
这个对象,abc
会在一定时机被 gc 自动清理,但是ss
对象本身的清理工作依赖于queue
,当ss
出现在queue
中时,说明其指向的对象已经无效,可以放心清理ss
了。
从上面的分析大家应该对reference
类有了基本的认识,但是上面也提到了,不同的引用,添加到referencequeue
的时机是不一样。下面介绍具体引用时再进行说明。
这里有个问题,如果创建引用对象是没有指定referencequeue
,引用对象会怎么样呢?这里需要了解reference
类内部的四种状态。
四种状态
每一时刻,reference
对象都处于下面四种状态中。这四种状态用reference
的成员变量queue
与next
(类似于单链表中的next)来标示。
referencequeue 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
这两个方法,画出了这四种状态的转移图,供大家参考:
对于一般程序员来说,这四种状态完全可以不用管。最后简单两句话总结上面的四种状态:
- 如果构造函数中指定了
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 entryextends weakreference