文档章节

guava底层源码简析

lizo
 lizo
发布于 2017/07/25 18:40
字数 1342
阅读 171
收藏 0

#程序员薪资揭榜#你做程序员几年了?月薪多少?发量还在么?>>>

摘要

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

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

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

介绍一下guava Cache基本框架

输入图片说明

  • 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在创建对象放到对应Segement中的时候,默认使用强引用(StrongValueReference.class),如果指定使用弱引用的时候,就会创建的是(WeakValueReference.class),参考guava cache基本框架可能更好理解。

合适最大容量

这个也是比较推荐的方法,根据业务需求,设置合适的缓存容量、这样超过容量以后,缓存就会按照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的在accessQueue中的前后节点,如果该entry不在队列中,则返回一个NullEntry的对象。这样做的好处就弥补了 链表的缺点

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

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

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

总结

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

© 著作权归作者所有

上一篇: Mybatis源码解析
下一篇: java编码详解
lizo

lizo

粉丝 60
博文 40
码字总数 50118
作品 0
杭州
程序员
私信 提问
加载中

评论(0)

使用Guava 缓存的一次重构

前端时间看了一个guava教程, 然后项目中正好用到一个缓存的场景,于是用它的缓存重构了原来的代码。 背景 业务背景如下:有一个配置项rootStaff,一个耗时的运算从rootStaff下拿到一堆关联的...

blackfaced
2018/07/10
0
0
简析guava cache线程安全设计哲学

1、 前言 guava cache是Google 出品的 Java 核心增强库的缓存部分,有着非常广泛的应用,有别于ConcurrentHashMap,guava cache可以按照多种策略来清理存储在其中的缓存值且保持很高的并发读...

aworker
2018/12/20
0
0
Java集合 使用Guava操作集合类

这是在一个课程中,看到了作者用 HashMap map = Maps.newHashMap()这种写法创建map对象感到很新颖,后来查到是用了google的Guava核心库,主要是使得代码更优美,易维护,易读,从大量的底层冗...

一曲图森破
2018/09/28
88
0
filecoin技术架构分析之十五:filecoin源码分析之节点运行逻辑

我是先河系统CTO杨尉,欢迎大加关注的的Github: waynewyang,本文是filecoin技术架构分析系列文章第十五章源码分析之节点运行逻辑。 分析基于的源码版本:go-filecoin master a0598a54(2019年...

深入浅出区块链
2019/03/10
0
0
Guava之eventBus异步事件总线的使用及源码分析

最近使用guava的eventBus,记录下。 1、如何使用 List-1.1 List-1.1中,方法subscribe是接收者,方法test_sendMsg中post消息后,方法subscribe就会收到消息。这是因为方法subscribe上有注解...

克虏伯
2019/01/04
398
0

没有更多内容

加载失败,请刷新页面

加载更多

数据倾斜

数据倾斜: 两种数据倾斜发生的现象: 80%情况下都发生挂了,只有极少20%情况下能把task执行完成 窄依赖:结构简单,如果发生数据丢失,方便查找丢失的数据 宽依赖:结构复杂,如何发生数据丢...

七宝1
今天
20
0
我的jdk源码(十一):ArrayList

一、概述 ArrayList类是AbstractList的子类,实现了具体的add(), set(), remove()等方法。它是一个可调整大小的数组可以用来存放各种形式的数据。 二、源码分析 (1) 类的声明,源码如下: ...

Java觉浅
昨天
24
0
vnc server,vnc server是什么,vnc工具推荐

nc server是一个用来共享linux服务器上资源给其他分布式用户的服务只要再一台linux系统的机器上安装vnc server,然后开启服务,其他机器就可以通过vncviewer访问这台机器上的共享资源,那么今...

兔子m
昨天
20
0
COLA的扩展性使用和源码研究

cola扩展点使用和设计初探 封装变化,可灵活应对程序的需求变化。 扩展点使用 步骤: 定义扩展点接口,类型可以是校验器,转换器,实体; 必须以ExtPt结尾,表示一个扩展点。 比如,我定义一个...

李福春carter
昨天
27
0
0、MySql第零章,安装及集群配置

MySql第零章,安装及集群配置 一、MySql安装 1、RPM安装 RPM安装,无法自定义一些安装路径和配置文件路径 ##以后再填坑 2、Generic安装 二进制 预编译 Generic,下载地址: https://cdn.m...

有一个小阿飞
昨天
17
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部