前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >WeakHashMap,源码解读[通俗易懂]

WeakHashMap,源码解读[通俗易懂]

作者头像
全栈程序员站长
发布于 2022-09-03 09:57:18
发布于 2022-09-03 09:57:18
40700
代码可运行
举报
运行总次数:0
代码可运行

大家好,又见面了,我是你们的朋友全栈

概述

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的定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class WeakHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V> {

与HashMap一样继承自AbstractMap类,并特地标注了Map接口,另外,WeakHashMap并没有实现Cloneable接口和Serializable接口。

再来看一下WeakHashMap的重要静态内部类Entry

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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。其源码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的重要字段:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 /** * 存储键值对的数组,一般是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定义的静态全局变量如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 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_THRESHOLDUNTREEIFY_THRESHOLDMIN_TREEIFY_CAPACITY三个静态全局变量,它们是用来进行单链表和红黑树转换的,在WeakHashMap中没有实现红黑树,因此不需要这三个变量。

下面再来看一下与弱键实现有关的重要方法expungeStaleEntries

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** * 从哈希表中删除被回收的引用 */
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,源码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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中的键。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/138373.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年5月3,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java中的WeakHashMap
WeakHashMap,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值,所以比较适合做缓存。
用户7886150
2021/04/26
5270
【Java入门提高篇】Day34 Java容器类详解(十五)WeakHashMap详解
在Java容器详解系列文章的最后,介绍一个相对特殊的成员:WeakHashMap,从名字可以看出它是一个 Map。它的使用上跟HashMap并没有什么区别,所以很多地方这里就不做过多介绍了,可以翻看一下前面HashMap中的内容。本篇主要介绍它与HashMap的不同之处。
弗兰克的猫
2018/10/10
5210
【Java入门提高篇】Day34 Java容器类详解(十五)WeakHashMap详解
死磕 java集合之WeakHashMap源码分析
WeakHashMap是一种弱引用map,内部的key会存储为弱引用,当jvm gc的时候,如果这些key没有强引用存在的话,会被gc回收掉,下一次当我们操作map的时候会把对应的Entry整个删除掉,基于这种特性,WeakHashMap特别适用于缓存处理。
彤哥
2019/07/08
4170
死磕 java集合之WeakHashMap源码分析
浅析WeakHashMap
在Java或者是Android编程中,我们一般都会使用到Map,比如HashMap这样的具体实现。更高级一点,我们可能会使用WeakHashMap。
技术小黑屋
2018/09/05
1K0
你不可不知的Java引用类型之——弱引用
弱引用是使用WeakReference创建的引用,弱引用也是用来描述非必需对象的,它是比软引用更弱的引用类型。在发生GC时,只要发现弱引用,不管系统堆空间是否足够,都会将对象进行回收。
弗兰克的猫
2018/11/20
2K0
滚雪球学Java(65-2):弱引用,强实现:探索Java的WeakHashMap
咦咦咦,各位小可爱,我是你们的好伙伴 bug菌,今天又来给大家手把手教学Java SE系列之集合篇知识点啦,赶紧出来哇,别躲起来啊,听我讲干货记得点点赞,赞多了我就更有动力讲得更欢哦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~
bug菌
2024/08/03
1080
滚雪球学Java(65-2):弱引用,强实现:探索Java的WeakHashMap
Spring扩展的集合LinkedMultiValueMap和ConcurrentReferenceHashMap解析
该接口的实现类为LinkedMultiValueMap,它其实就是委托了一个LinkedHashMap<K, List<V>>来处理所有的方法。
算法之名
2020/05/26
1.7K0
Java 引用类型-Java快速入门教程
在 JDK 1.2 以前,Java 中的引用定义很传统:如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。在这种定义下,一个对象只有引用和没有被引用两个状态。
jack.yang
2025/04/05
510
WeakHashMap理解
从名字可以得知主要和Map有关,不过还有一个Weak,我们就更能自然而然的想到这里面还牵扯到一种弱引用结构,因此想要彻底搞懂,我们还需要知道四种引用。如果你已经知道了,可以跳过。
全栈程序员站长
2022/09/04
5220
WeakHashMap理解
深入理解JVM垃圾收集机制
程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后也会消失,因此不需要对这三个区域进行垃圾回收。垃圾回收主要是针对 Java 堆和方法区进行。
李红
2019/05/29
3790
Java集合之WeakHashMap[通俗易懂]
WeakHashMap继承于AbstractMap,同时实现了Map接口。
全栈程序员站长
2022/09/04
6690
Java集合之WeakHashMap[通俗易懂]
Java--Java中的四种引用
Java中存在四种引用,StrongReference(强引用) 、SoftReferenc(软引用) 、WeakReferenc(弱引用)、PhantomReference(虚引用).虽然不常用,但是对于理解Java的回收等级还是很有帮助的,一句话来说这些引用只是不同回收等级的一种表现形式.
屈定
2018/09/27
5210
Java--Java中的四种引用
Java 容器详解:使用与案例
容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。
小万哥
2023/06/04
7250
Java 容器详解:使用与案例
WeakHashMap
WeakHashMap,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值,
全栈程序员站长
2022/09/05
3740
基于JDK1.8,Java容器源码分析
在 IDEA 中 double shift 调出 Search EveryWhere,查找源码文件,找到之后就可以阅读源码。
李红
2019/06/01
5210
基于JDK1.8,Java容器源码分析
WeakHashMap的原理
WeakHashMap和HashMap一样,WeakHashMap也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以为null。不过WeakHashMap的键是“弱键”(注:源码中Entry中的定义是这样的:private static class Entry<K,V> extends WeakReference implements Map.Entry<K,V>,即Entry实现了WeakReference类),当WeakHashMap某个键不再正常使用时,会被从WeakHashMap自动删除。更精确的说,对于一个给定的键,其映射的存在并不能阻止垃圾回收器对该键的丢弃,这就使该键称为被终止的,被终止,然后被回收,这样,这就可以认为该键值对应该被WeakHashMap删除。因此,WeakHashMap使用了弱引用作为内部数据的存储方案,,WeakHashMap可以作为简单缓存表的解决方案,当系统内存不足时,垃圾收集器会自动的清除没有在任何其他地方被引用的键值对。如果需要用一张很大的Map作为缓存表时,那么可以考虑使用WeakHashMap。
全栈程序员站长
2022/09/02
2550
BATJ面试必会之 Java 容器篇
容器主要包括 Collection 和 Map 两种,Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表。
乔戈里
2019/03/19
7000
BATJ面试必会之 Java 容器篇
Java容器(List、Set、Map)知识点快速复习手册(下)
http://wiki.jikexueyuan.com/project/java-collection/hashset.html
Rude3Knife的公众号
2019/08/07
5400
Java容器(List、Set、Map)知识点快速复习手册(下)
Java集合的总结
添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 oldCapacity + (oldCapacity >> 1),也就是旧容量的 1.5 倍。
大学里的混子
2019/03/13
4060
大数据基础系列之JAVA引用详解
一,四种引用介绍 从Java SE2开始,就提供了四种类型的引用:强引用、软引用、弱引用和虚引用。Java中提供这四种引用类型主要有两个目的:第一是可以让程序员通过代码的方式决定某些对象的生命周期;第二是有利于JVM进行垃圾回收。 1,强引用 强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。 test对象未消亡之前,object和str
Spark学习技巧
2018/01/31
5850
相关推荐
Java中的WeakHashMap
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验