Java中的WeakHashMap

Java中的WeakHashMap楔子WeakHashMap,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值,所以比较适合做缓存。WeakHashMap的这种特性比较适合实现类似本地、堆内缓存的存储机制——缓存的失效依赖于GC收集器的行为WeakHashMap的定义如下:publicclassWeakHashMap<K,V>extendsAbstract…

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

楔子

WeakHashMap,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值,所以比较适合做缓存。

WeakHashMap的这种特性比较适合实现类似本地、堆内缓存的存储机制——缓存的失效依赖于GC收集器的行为

WeakHashMap的定义如下:

public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>

简单来说,WeakHashMap实现了Map接口,基于hash-table实现,在这种Map中,key的类型是WeakReference。如果对应的key被回收,则这个key指向的对象会被从Map容器中移除。

WeakHashMap跟普通的HashMap不同,WeakHashMap的行为一定程度上基于垃圾收集器的行为,因此一些Map数据结构对应的常识在WeakHashMap上会失效——size()方法的返回值会随着程序的运行变小,isEmpty()方法的返回值会从false变成true等等。

实例

此例子中声明了两个Map对象,一个是HashMap,一个是WeakHashMap,同时向两个map中放入a、b两个对象,当HashMap remove掉a 并且将a、b都指向null时,WeakHashMap中的a将自动被回收掉。

出现这个状况的原因是,对于a对象而言,当HashMap remove掉并且将a指向null后,除了WeakHashMap中还保存a外已经没有指向a的指针了,所以WeakHashMap会自动舍弃掉a,而对于b对象虽然指向了null,但HashMap中还有指向b的指针。

弱引用(WeakReference)的特性是:当gc线程发现某个对象只有弱引用指向它,那么就会将其销毁并回收内存。

package com.sino.daily.code_2019_9_1;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;

/** * create by 2020-04-28 22:45 * * @author caogu */
public class WeekHash { 
   
    public static void main(String[] args) { 
   
        String a = new String("a");
        String b = new String("b");
        Map map = new HashMap();
        map.put(a, "aaa");
        map.put(b, "bbb");

        Map weakmap = new WeakHashMap();
        weakmap.put(a, "aaa");
        weakmap.put(b, "bbb");

        map.remove(a);
        a = null;
        b = null;

        System.gc();
        
        Iterator i = map.entrySet().iterator();
        while (i.hasNext()) { 
   
            Map.Entry en = (Map.Entry) i.next();
            System.out.println("map:" + en.getKey() + ":" + en.getValue());
        }

        Iterator j = weakmap.entrySet().iterator();
        while (j.hasNext()) { 
   
            Map.Entry en = (Map.Entry) j.next();
            System.out.println("weakmap:" + en.getKey() + ":" + en.getValue());

        }
    }
}

在这里插入图片描述

典型使用场景: tomcat两级缓存

tomcat的源码里,实现缓存时会用到WeakHashMap

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);
    }
}

源码中有eden和longterm的两个map,对jvm堆区有所了解的话,可以猜测出tomcat在这里是使用ConcurrentHashMap和WeakHashMap做了分代的缓存。

  • 在put方法里,在插入一个k-v时,先检查eden缓存的容量是不是超了。没有超就直接放入eden缓存,如果超了则锁定longterm将eden中所有的k-v都放入longterm。再将eden清空并插入k-v。
  • 在get方法中,也是优先从eden中找对应的v,如果没有则进入longterm缓存中查找,找到后就加入eden缓存并返回。

经过这样的设计,相对常用的对象都能在eden缓存中找到,不常用(有可能被销毁的对象)的则进入longterm缓存。而longterm的key的实际对象没有其他引用指向它时,gc就会自动回收heap中该弱引用指向的实际对象,弱引用进入引用队列。longterm调用expungeStaleEntries()方法,遍历引用队列中的弱引用,并清除对应的Entry,不会造成内存空间的浪费。

利用WeakHashMap实现内存缓存

可以看出,WeakHashMap的这种特性比较适合实现类似本地、堆内缓存的存储机制——缓存的失效依赖于GC收集器的行为。假设一种应用场景:我们需要保存一批大的图片对象,其中values是图片的内容,key是图片的名字,这里我们需要选择一种合适的容器保存这些对象。

使用普通的HashMap并不是好的选择,这些大对象将会占用很多内存,并且还不会被GC回收,除非我们在对应的key废弃之前主动remove掉这些元素。WeakHashMap非常适合使用在这种场景下,下面的代码演示了具体的实现:

WeakHashMap<UniqueImageName, BigImage> map = new WeakHashMap<>();
BigImage bigImage = new BigImage("image_id");
UniqueImageName imageName = new UniqueImageName("name_of_big_image"); //强引用

map.put(imageName, bigImage);
assertTrue(map.containsKey(imageName));

imageName = null; //map中的values对象成为弱引用对象
System.gc(); //主动触发一次GC

await().atMost(10, TimeUnit.SECONDS).until(map::isEmpty);

首先,创建一个WeakHashMap对象来存储BigImage实例,对应的key是UniqueImageName对象,保存到WeakHashMap里的时候,key是一个弱引用类型。

然后,我们将imageName设置为null,这样就没有其他强引用指向bigImage对象,按照WeakHashMap的规则,在下一次GC周期中会回收bigImage对象。

通过System.gc()主动触发一次GC过程,然后可以发现WeakHashMap成为空的了。

强引用、软引用和弱引用

  • 强引用(Strong Reference)
  • 软引用(Soft Reference)
  • 弱引用 (WeakReference)
强引用

被强引用指向的对象,绝对不会被垃圾收集器回收。Integer prime = 1;,这个语句中prime对象就有一个强引用。

软引用

被SoftReference指向的对象可能会被垃圾收集器回收,但是只有在JVM内存不够的情况下才会回收;如下代码可以创建一个软引用:

	Integer prime = 1;  
	SoftReference<Integer> soft = new SoftReference<Integer>(prime);
	prime = null;
弱引用

当一个对象仅仅被WeakReference引用时,在下个垃圾收集周期时候该对象就会被回收。我们通过下面代码创建一个WeakReference:

	Integer prime = 1;  
	WeakReference<Integer> soft = new WeakReference<Integer>(prime);
	prime = null;

当把prime赋值为null的时候,原prime对象会在下一个垃圾收集周期中被回收,因为已经没有强引用指向它。

参考文献

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

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

赞 (0)
全栈程序员-站长的头像全栈程序员-站长

相关推荐

  • LoadRunner教程(7)-LoadRunner 创建测试场景

    LoadRunner教程(7)-LoadRunner 创建测试场景首先打开controller创建一个场景,有手工场景和目标场景设置两个选项,先选择手工场景手工场景设置GlobalSchedule:Scenario初始化:所有用户同时初始化,每隔多少秒初始化多少用户,每个用户运行之前初始化启动用户:多少用户启动,同时启动,每隔多长时间启动多少用户运行时间:持续运行直到结束,持续运行时间用户退出:用户同时退出,每隔多长时间…

    2022年5月10日
  • 【记录】mac使用PyCharm中Python版本不对应的解决方法

    【记录】mac使用PyCharm中Python版本不对应的解决方法在使用PyCharm进行tensorflow学习时,发现mac中还有Python2.7的旧版本,并且说明建议使用新版本。经过搜索以及多个方法的试错,突然发现我用的是macOSBigSur,而惊喜的是已经出的新版本macOSMonterey已经把Python2.7移除了,也许是因为这个原因,在安装了Monterey后,就可以非常顺利的使用Python3.7以及anaconda和tensorflow2.0了。然而在之后的一天我使用anaconda运行py程序时发现了这个问题:Process

    2022年8月27日
  • mysql字符串截取函数「建议收藏」

    mysql字符串截取函数「建议收藏」mysql字符串截取函数left(),right(),substring(),substring_index();还有mid(),substr()。其中,mid(),substr()等价于substring()函数,substring()的功能非常强大和灵活。1.字符串截取:left(str,length)mysql>selectleft(‘example….

    2022年10月2日
  • python 爬虫之selenium可视化爬虫

    python 爬虫之selenium可视化爬虫文章目录前文回顾快速入门元素定位页面操作等待方式扩展程序加载前文回顾一文带你了解Python爬虫(一)——基本原理介绍一文带你了解Python爬虫(二)——四种常见基础爬虫方法介绍之所以把selenium爬虫称之为可视化爬虫主要是相较于前面所提到的几种网页解析的爬虫方式selenium爬虫主要是模拟人的点击操作selenium驱动浏览器并进行操作的过程是可以观察到的就类似于你在看着别人在帮你操纵你的电脑,类似于别人远程使用你的电脑当然了,selenium也有无界面模式快速入门sel

    2022年5月9日
  • JAVA Calendar方法详解「建议收藏」

    JAVA Calendar方法详解「建议收藏」 究竟什么是一个Calendar呢?中文的翻译就是日历,那我们立刻可以想到我们生活中有阳(公)历、阴(农)历之分。它们的区别在哪呢?比如有:月份的定义-阳`(公)历一年12个月,每个月的天数各不同;阴(农)历,每个月固定28天每周的第一天-阳(公)历星期日是第一天;阴(农)历,星期一是第一天实际上,在历史上有着许多种纪元的方法。它们的差异实在太大了,比如说一个人的生日是”八月八日”

    2022年6月1日
  • 【夯实基础】Spring在ssh中的作用

    【夯实基础】Spring在ssh中的作用

    2022年1月22日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号