WeakHashMap,源码解读
概述
WeakHashMap也是Map接口的一个实现类,它与HashMap相似,也是一个哈希表,存储key-value pair,而且也是非线程安全的。不过WeakHashMap并没有引入红黑树来尽量规避哈希冲突带来的影响,内部实现只是数组+单链表。此外,WeakHashMap与HashMap最大的不同之处在于,WeakHashMap的key是“弱键”(weak keys),即当一个key不再正常使用时,key对应的key-value pair将自动从WeakHashMap中删除,在这种情况下,即使key对应的key-value pair的存在,这个key依然会被GC回收,如此以来,它对应的key-value pair也就被从map中有效地删除了。
Java的四种引用
在正式进入WeakHashMap源码之前,我们需要先对“弱引用”有一个基本的认识,为此这里先介绍一下JDK 1.2开始推出的四种引用:
- 强引用(Strong Reference) 强引用是指在程序代码之中普遍存在的,类似
Objective obj = new Object()
这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。 - 软引用(Soft Reference) 软引用是用来描述一些还有用但并非必需的对象,对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK 1.2之后,提供了SoftReference类来实现软引用。
- 弱引用(Weak Reference) 弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一点,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引用。
- 虚引用(PhantomReference) 虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2之后,提供了PhantomReference类来实现虚引用。
我们说WeakHashMap的key是weak-keys,即是说这个Map实现类的key值都是弱引用。
底层实现
先来看一下WeakHashMap的定义:
public class WeakHashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V> {
与HashMap一样继承自AbstractMap类,并特地标注了Map接口,另外,WeakHashMap并没有实现Cloneable接口和Serializable接口。
再来看一下WeakHashMap的重要静态内部类Entry:
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
V value;
final 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的构造函数,说明Entry的key是弱引用
//同时key值会进入引用队列queue中,等待被处理
super(key, queue);
//显示定义了value,说明了Entry的value是强引用
this.value = value;
this.hash = hash;
this.next = next;
} @SuppressWarnings("unchecked")
public K getKey() {//在获取key时需要unmaskNull,因为对于null的key,是用WeakHashMap的内部成员属性来表示的
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 Objects.hashCode(k) ^ Objects.hashCode(v);
} public String toString() {
return getKey() + "=" + getValue();
}
}
这里用到了unmaskNull()
方法,它的作用是用一个空的Object对象来表示null值得key。其源码如下:
private static final Object NULL_KEY = new Object();
/** * 当key为null时,使用NULL_KEY表示key */
private static Object maskNull(Object key) {
return (key == null) ? NULL_KEY : key;
}
可以看到Entry<K,V>继承自WeakReference类,那么对于Entry类中需要定义为弱引用的字段,直接传入父类的构造函数即可,如代码中看到的key,这也实现了我们前面说的“弱键”。而value值则赋予了强引用,但这并不影响,我们在稍后会介绍一个WeakHashMap.expungeStaleEntries
方法,该方法会把弱键对应的key-value整个赋为null,以帮助GC将其回收。
再来看一下WeakHashMap的重要字段:
/** * 存储键值对的数组,一般是2的幂 */
Entry<K,V>[] table;
/** * 键值对的实际个数 */
private int size;
/** * 扩容的临界值,通过capacity * load factor可以计算出来。超过这个值HashMap将进行扩容 * @serial */
private int threshold;
/** * 负载因子 */
private final float loadFactor;
/** * 引用队列,用于保存会被GC回收的弱键 */
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
/** * 记录HashMap被修改结构的次数。 * 修改包括改变键值对的个数或者修改内部结构,比如rehash * 这个域被用作HashMap的迭代器的fail-fast机制中(参考ConcurrentModificationException) */
int modCount;
与HashMap相比,WeakHashMap少了entrySet字段,而多了一个引用队列queue。
WeakHashMap定义的静态全局变量如下:
private static final int DEFAULT_INITIAL_CAPACITY = 16; /** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30. */
private static final int MAXIMUM_CAPACITY = 1 << 30; /** * The load factor used when none specified in constructor. */
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
与HashMap相比,缺少了TREEIFY_THRESHOLD、UNTREEIFY_THRESHOLD、MIN_TREEIFY_CAPACITY三个静态全局变量,它们是用来进行单链表和红黑树转换的,在WeakHashMap中没有实现红黑树,因此不需要这三个变量。
下面再来看一下与弱键实现有关的重要方法expungeStaleEntries
:
/** * 从哈希表中删除被回收的引用 */
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) x;//e为要清理的Entry
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; // 把value赋值为null,帮助GC回收强引用的value
size--;
break;
}
prev = p;
p = next;
}
}
}
}
从WeakHashMap.Entry的源码中我们看到,weak keys的值会被保存到引用队列中,该方法就说将引用队列中保存的弱键对应的Entry从单链表中删除,即删除哈希表中被GC回收了的键值对。在WeakHashMap定义的增、删、改、查方法中,都要调用该方法。
应用场景
缓存
缓存是内存泄漏的一个常见来源,一旦你把对象引用放到缓存中,它就很容易被遗忘掉,从而使得它不再有用之后很长一段时间内仍然留在缓存中。
对于这个问题,有几种可能的解决方案。如果你正好要实现这样的缓存:只要在缓存之外存在对某个项的键的引用,该项就有意义,那么就可以用WeakHashMap代表缓存。当缓存中的项过期之后,它们就会自动被删除(要注意的是,只有当所要的缓存项的生命周期是由该键的外部引用而不是由值决定时,WeakHashMap才有用处)。
Tomcat就用WeakHashMap实现了它的并发缓存ConcurrentCache,源码如下:
package org.apache.tomcat.util.collections; import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap; public final class ConcurrentCache<K,V> { private final int size; private final Map<K,V> eden; private final Map<K,V> longterm; public ConcurrentCache(int size) {
this.size = size;
this.eden = new ConcurrentHashMap<>(size);
this.longterm = new WeakHashMap<>(size);
} public V get(K k) {
V v = this.eden.get(k);
if (v == null) {
synchronized (longterm) {
v = this.longterm.get(k);
}
if (v != null) {
this.eden.put(k, v);
}
}
return v;
} public void put(K k, V v) {
if (this.eden.size() >= size) {
synchronized (longterm) {
this.longterm.putAll(this.eden);
}
this.eden.clear();
}
this.eden.put(k, v);
}
}
监听器和其他回调
内存泄漏的另一个常见来源是监听器和其他回调。如果你在实现的是客户端注册回调却没有显式地取消注册的API,除非你采取某些动作,否则它们就会积聚。确保回调立即被当作垃圾回收的最佳方法是只保存它们的弱引用(weak reference),可以只将它们保存成WeakHashMap中的键。
------------------------推荐阅读------------------------
2019年JVM最新面试题,必须收藏它
最全面的阿里多线程面试题,你能回答几个?
Java面试题:Java中的集合及其继承关系
花了近十年的时间,整理出史上最全面Java面试题
WeakHashMap,源码解读的更多相关文章
- WeakHashMap源码解读
1. 简介 本文基于JDK8u111的源码分析WeakHashMap的一些主要方法的实现. 2. 数据结构 就数据结构来说WeakHashMap与HashMap原理差不多,都是拉链法来解决哈希冲突. ...
- jdk1.8.0_45源码解读——Map接口和AbstractMap抽象类的实现
jdk1.8.0_45源码解读——Map接口和AbstractMap抽象类的实现 一. Map架构 如上图:(01) Map 是映射接口,Map中存储的内容是键值对(key-value).(02) A ...
- SDWebImage源码解读之SDWebImageDownloaderOperation
第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...
- SDWebImage源码解读 之 NSData+ImageContentType
第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...
- SDWebImage源码解读 之 UIImage+GIF
第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...
- SDWebImage源码解读 之 SDWebImageCompat
第三篇 前言 本篇主要解读SDWebImage的配置文件.正如compat的定义,该配置文件主要是兼容Apple的其他设备.也许我们真实的开发平台只有一个,但考虑各个平台的兼容性,对于框架有着很重要的 ...
- SDWebImage源码解读_之SDWebImageDecoder
第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...
- SDWebImage源码解读之SDWebImageCache(上)
第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...
- SDWebImage源码解读之SDWebImageCache(下)
第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...
随机推荐
- 21-django-pure-pagination分页
一.django-pure-pagination分页 github上的描述(django-pure-pagination provides advanced pagination features a ...
- 【algo&ds】9.拓扑排序、AOV&AOE、关键路径问题
对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性 ...
- PHP目前常见的五大运行模式
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/xujingzhong0077/artic ...
- 图数据库 Nebula Graph 的安装部署
Nebula Graph:一个开源的分布式图数据库.作为唯一能够存储万亿个带属性的节点和边的在线图数据库,Nebula Graph 不仅能够在高并发场景下满足毫秒级的低时延查询要求,还能够实现服务高可 ...
- 汇总:ASP.NET Core中HttpContext获取传参数据,有哪些方式
一.原生方式: 1.POST(以ajax请求为案例,教大家用法) $.ajax({ type: "post", dataType: "json", cache: ...
- vue关于mock的简单使用
一.mock 1.简介 mock是一个模拟数据生成器,旨在帮助前端独立于后端进行开发,帮助编写单元测试.其可模拟 Ajax 并返回模拟数据,使前端不用去调用后端的接口,方便测试. 2.vue直接使用m ...
- 编译原理之不懂就问-First集
老师PPT: 这条语言实在是..通俗易懂
- Young and Beautiful 新红楼剪辑(by up)
up剪辑的新红楼视频,配上Young and Beautiful 这首英文歌,很优美^_^
- 四、读取一系列dcm图片,然后重新写入
一.程序功能 读取一系列的CT dcm图片,然后重新写入到一个文件夹 二.代码 #pragma warning(disable:4996) #include "itkGDCMImageIO. ...
- windows下配置ngnix服务器经常出现503问题解决办法
自己网站在windows server2008下安装的ngnix,然后配置php,网站访问流量并不大,但是经常出现503问题.经过查看ngnix服务器错误日志,发现: (10061: No conne ...