摘要

guava的缓存相信很多人都有用到,

Cache<String, String> cache = CacheBuilder.newBuilder()
.expireAfterWrite(100, TimeUnit.SECONDS)
.maximumSize(10).build();

也常用的方法是设置过期时间。但使用过程中会遇到一些问题:当过期时间到了,缓存中的对象真的会立即被释放吗?当缓存达到容量以后,如何高效的剔除缓存?guava cache的底层数据结构是如何的?带着这些问题,一起来看看guava cache的源码

介绍一下guava缓存的基本框架

  • LoacalCache:实现了currentMap接口,保存了一些配置信息,例如失效时间、容量等。是保存所有缓存最外层的容器
  • segment:为了高并发,借鉴了currentMap中的分段锁机制,segment可以理解是LocalCache中的一部分,不同的segment之间并发不受影响。每次操作根据key进行hash,保证了同一个key的put和set都在同一个segment中。segment中还有两个分别队列用于保存软引用或者弱引用对象回收后的引用
  • refrenceEntry:保存一个缓存key-val的对象,类似map中的entry,只不过map中entry保存的对象的直接进行,而refrenceEntry这是在中间多了一层valueReference
  • valueReference:如果是强引用,则直接保存对象的直接引用,当然也可以使用软引用的方法。

其实通过和CurrentHashMap最类比比较好理解,只不过guava缓存在其基础上增强了缓存过期的机制:

  1. 最大对象个数限制
  2. 超时机制
  3. 弱引用或者软引用

guava会oom吗

答案是肯定的,当我们设置缓存用不过期(或者很长),缓存的对象不限个数(或者很大),例如

Cache<String, String> cache = CacheBuilder.newBuilder()
.expireAfterWrite(100000, TimeUnit.SECONDS)
.build();

不断向guava加入缓存大字符串,最终将能oom,解决这种办法:

使用弱引用或者软应用

Cache<String, String> cache = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.SECONDS)
.weakValues().build();

guava在创建对象放在一个map(LocalCache.class)的时候,默认使用强引用(StrongValueReference.class),如果指定使用弱引用的时候,就会创建的是(WeakValueReference.class)

合适最大容量

这个也是比较推荐的方法,根据业务需求,设置合适的缓存容量、这样超过容量以后,缓存就会按照LRU的方式回收缓存。

CacheBuilder.maximumSize(10)

guava缓存到期就会立即清除吗

guava清楚过期缓存的机制是什么,是单独使用线程来扫描吗?不是的,是在每次进行缓存操作的时候,如get()或者put()的时候,判断缓存是否过期。核心代码

void expireEntries(long now) {
drainRecencyQueue(); //多线并发的情况下,防止误删access ReferenceEntry<K, V> e;
while ((e = writeQueue.peek()) != null && map.isExpired(e, now)) {
if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {
throw new AssertionError();
}
}
while ((e = accessQueue.peek()) != null && map.isExpired(e, now)) {
if (!removeEntry(e, e.getHash(), RemovalCause.EXPIRED)) {
throw new AssertionError();
}
}
}

其中 writeQueue是保存按照写入缓存先后时间的队列,每次get或者put都可能触发触发这个方法。accessQueue同理,对应的是最后访问失效时间的功能。
因此可以看出,一个如果一个对象放入缓存以后,不在有任何缓存操作(包括对缓存其他key的操作),那么该缓存不会主动过期的。不过这种情况是极端情况下才会出现。

guava如何找出最久未使用的缓存

在上面也说到了,是用accessQueue,这个队列的实现比较复杂。这个队列其实是按照最久未使用的顺序存放的缓存对象(ReferenceEntry)的。由于会经常进行元素的移动,例如把访问过的对象放到队列的最后。ReferenceEntry这个在前面框架图里面说到了,使用来保存key-val的,其中接口包含一些特殊方法:

@Override
public ReferenceEntry<K, V> getNextInAccessQueue() {
throw new UnsupportedOperationException();
} @Override
public void setNextInAccessQueue(ReferenceEntry<K, V> next) {
throw new UnsupportedOperationException();
} @Override
public ReferenceEntry<K, V> getPreviousInAccessQueue() {
throw new UnsupportedOperationException();
} @Override
public void setPreviousInAccessQueue(ReferenceEntry<K, V> previous) {
throw new UnsupportedOperationException();
}

这样通过ReferenceEntry可以就可以判断该entry的后面节点,如果不在队列中,则返回一个NullEntry的对象。这样做的好处就弥补了 链表的缺点

  • 判断一个ReferenceEntry是否在队列中,只要判断该ReferenceEntry的前一个引用是否是NullEntry,不需要便利整个链表

并且可以很方便的更新和删除链表中的节点,因为每次访问的时候都可能需要更新该链表,放入到链表的尾部,这样,每次从access中拿出的头节点就是最久未使用的。 并且,如果按照访问时间来删除缓存的时候,只要从队列里找出第一个访问没有超时的对象,那么之前遍历的缓存都是应该删除的,这样就不需要遍历整个缓存的对象来判断。

对应的writeQueue用来保存最久未更新的缓存队列,实现方式和accessQueue一样。

总结

可以看出,guava缓存的原型是CurrentHashMap,在其基础上考虑如果判断缓存是否过期。底层的一些数据结构也是用的十分巧妙。如果能仔细的看看源码,相信对你也有一定的帮助

guava缓存底层实现的更多相关文章

  1. Guava缓存器源码分析——删除消息

    Guava缓存器的删除消息机制 测试代码——             LoadingCache<String, Integer> cache = CacheBuilder.newBuild ...

  2. Guava缓存器源码分析——缓存统计器

    Guava缓存器统计器实现: 全局统计器——         1.CacheBuilder的静态成员变量Supplier<StatsCounter> CACHE_STATS_COUNTER ...

  3. Google Guava缓存实现接口的限流

    一.项目背景 最近项目中需要进行接口保护,防止高并发的情况把系统搞崩,因此需要对一个查询接口进行限流,主要的目的就是限制单位时间内请求此查询的次数,例如1000次,来保护接口. 参考了 开涛的博客聊聊 ...

  4. springboot集成Guava缓存

    很久没有写博客了,这段时间一直忙于看论文,写论文,简直头大,感觉还是做项目比较舒服,呵呵,闲话不多说,今天学习了下Guava缓存,这跟Redis类似的,但是适用的场景不一样,学习下吧.今天我们主要是s ...

  5. spring中添加google的guava缓存(demo)

    1.pom文件中配置 <dependencies> <dependency> <groupId>org.springframework</groupId> ...

  6. guava缓存设置return null一直报错空指针

    guava缓存设置return null一直报错空指针 因为缓存不允许返回为空

  7. spring boot使用guava缓存

    1.pom中插入依赖: <!--guava缓存cache--> <dependency> <groupId>com.google.guava</groupId ...

  8. guava缓存批量获取的一个坑

    摘要 Guava Cache是Google开源的Java工具集库Guava里的一款缓存工具,一直觉得使用起来比较简单,没想到这次居然还踩了一个坑 背景 功能需求抽象出来很简单,就是将数据库的查询sth ...

  9. guava缓存第一篇

    guava缓存主要有2个接口,Cache和LoadingCache. Cache,全类名是com.google.common.cache.Cache,支持泛型.Cache<K, V>. L ...

随机推荐

  1. html打造动画【系列1】- 萌萌的大白

    每个人心中都有一个暖暖的大白,blingbling的大眼睛~软软的肚子~宽厚的肩膀~善良的心肠~如果可以,我愿意沦陷在大白的肚子里永远不出来,哈哈~毛球要失宠咯~ 哈哈哈 每个人都是独立的个体,大白也 ...

  2. Nmap在实战中的高级用法

    Nmap提供了四项基本功能(主机发现.端口扫描.服务与版本侦测.OS侦测)及丰富的脚本库.Nmap既能应用于简单的网络信息扫描,也能用在高级.复杂.特定的环境中:例如扫描互联网上大量的主机:绕开防火墙 ...

  3. bettercap实现内网Dns欺骗

    目的 让内网的所有计算机浏览网页的时候, 出现我的钓鱼页面 准备 kali系统 Bettercap dns文件 通过ifconfig查看当前计算机的ip, 我这边为, 192.168.1.150 创建 ...

  4. 动态读取文件持续显示在UI上

    private void DisplayLogInfo(FileInfo _LastFile) { if (_LastFile != null) { StreamReader sr = null; t ...

  5. ex3多类问题和NN中的前向传播

    ​ 昨日去了趟无锡,前天下了暴雨,所以昨天给我的感觉天气很好,天蓝云白的,以后在这边学习估计也是一件很爽的事情,且昨日通知书业寄到学校了,附赠了一份研究生数学建模的传单,我搜了搜近几年的题目,感觉统计 ...

  6. 通过 itms:services://? 在线安装ipa ,跨过app-store

    1.需要一个html文件,引导下载用户在线安装ipa <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN&quo ...

  7. 避免循环做SQL操作

    经常犯的错误是把一个SQL 操作放置到一个循环中, 这就导致频繁的访问数据库,更重要的是, 这会直接导致脚本的性能低下.以下的例子, 你能够把一个循环操作重置为一个单一的SQL语句. foreach ...

  8. PGI Compiler for OpenACC Output Syntax Highlighting

    PGI Compiler for OpenACC Output Syntax Highlighting When use the PGI compiler to compile codes with ...

  9. Redis初体验

    简介        Redis是一个速度非常快的非关系型数据库,它不仅性能强劲,而且还具有复制特性以及为解决问题而生的独一无二的数据模型.作为键值型数据库,Redis支持5中数据类型:字符串,列表,集 ...

  10. 使用vue实现tab操作

    在使用jQuery类库实现tab功能时,是获取鼠标在mousenter或click时的index值,然后切换到当前的标题和内容,把其他的标题和内容的状态去掉: $('.tab .title').fin ...