摘要

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. 网页 cookie

    定义: 从JavaScript的角度看,cookie 就是一些字符串信息.这些信息存放在客户端的计算机中,用于客户端计算机与服务器之间传递信息. 使用:document.cookie = 'usern ...

  2. ssh代理上网

    背景: 公司开发机没有外网,但可以通过ssh连接到另一台可以上公网的机器,所以想通过ssh代理的方式上网,简单又方便,而且需要的时候上,不需要的时候也可以不上 配置: 超级简单 在开发机上建立ssh隧 ...

  3. 用JMX远程监控Tomcat

    要通过JMX远程监控Tomcat,首先需要进行Tomcat的JMX远程配置. 注意:此配置添加在catalina.bat文件开头的注释行(rem)后面即可. 不需鉴权的配置: 先修改Tomcat的启动 ...

  4. 由 “无法使用从远程表选择的 lob 定位符” 错误而引导出来的一系列问题解决方案

    周一上班遇到一个数据加工问题:无法使用从远程表选择的 lob 定位符,由于数据源表不是自己的,不能对源数据做修改,于是我打起了存储过程的主意 我们公司的存过是分三步走,第一层是同步源数据,第二层是对一 ...

  5. 快速压缩PNG文件在线工具

    https://tinypng.com/ 直接拖移要压缩文件即可

  6. VSTO在幻灯片里面添加按钮对象

    //添加Form窗体,窗体中添加Image控件,单击弹出"PPT"信息提示 //命名引用:using MF = Microsoft.Vbe.Interop.Forms; priva ...

  7. OS X 和iOS 中的多线程技术(上)

    OS X 和iOS 中的多线程技术(上) 本文梳理了OS X 和iOS 系统中提供的多线程技术.并且对这些技术的使用给出了一些实用的建议. 多线程的目的:通过并发执行提高 CPU 的使用效率,进而提供 ...

  8. 转 使用HAProxy,PHPRedis,和MySQL支撑10亿请求每周架构细节

    [编者按]在公司的发展中,保证服务器的可扩展性对于扩大企业的市场需要具有重要作用,因此,这对架构师提出了一定的要求.Octivi联合创始人兼软件架构师Antoni Orfin将向你介绍一个非常简单的架 ...

  9. Mac机上安装nvm遇到的坑--nvm command not found

    在Mac机上,我最开始是通过homebrew来安装nvm,如下: $ brew install nvm 安装完成后,在命令行输入nvm list,发现出现如下提示: What?!!我不是才安装了nvm ...

  10. 【开源】做了一个WinForm窗体的投影组件,能够为窗口添加影子效果

    最近手头上的项目终于忙得差不多了,想起好久没有更新了的NanUI,再看着每天QQ群未读消息闪烁的标志,突然才发现似乎愧对了群里各位喜爱NanUI的朋友们.于是乎,就想趁这几天有时间,好好的修复一下Na ...