JVM 缓存,是堆缓存。其实就是创建一些全局容器,比如List、Set、Map等。
这些容器用来做数据存储。
这样做的问题:
不能按照一定的规则淘汰数据,如 LRU,LFU,FIFO 等。
清除数据时的回调通知
并发处理能力差,针对并发可以使用CurrentHashMap,但缓存的其他功能需要自行实现缓存过期处理,缓存数据加载刷新等都需要手工实现
Guava是Google提供的一套Java工具包,而Guava Cache是一套非常完善的本地缓存机制(JVM缓存)。
Guava cache的设计来源于CurrentHashMap,可以按照多种策略来清理存储在其中的缓存值且保持很高的并发读写性能。
对性能有非常高的要求不经常变化占用内存不大有访问整个集合的需求数据允许不时时一致 2、Guava Cache 的优势
缓存过期和淘汰机制
在GuavaCache中可以设置Key的过期时间,包括访问过期和创建过期
GuavaCache在缓存容量达到指定大小时,采用LRU的方式,将不常使用的键值从Cache中删除并发处理能力
GuavaCache类似CurrentHashMap,是线程安全的。
提供了设置并发级别的api,使得缓存支持并发的写入和读取
采用分离锁机制,分离锁能够减小锁力度,提升并发能力
分离锁是分拆锁定,把一个集合看分成若干partition, 每个partiton一把锁。ConcurrentHashMap就是分了16个区域,这16个区域之间是可以并发的。GuavaCache采用Segment做分区。更新锁定
一般情况下,在缓存中查询某个key,如果不存在,则查源数据,并回填缓存。(Cache Aside Pattern)在高并发下会出现,多次查源并重复回填缓存,可能会造成源的宕机(DB),性能下降
GuavaCache可以在CacheLoader的load方法中加以控制,对同一个key,只让一个请求去读源并回填缓存,其他请求阻塞等待。集成数据源
一般我们在业务中操作缓存,都会操作缓存和数据源两部分GuavaCache的get可以集成数据源,在从缓存中读取不到时可以从数据源中读取数据并回填缓存监控缓存加载/命中情况
统计
三、Guava Cache创建方式
GuavaCache有两种创建方式:
CacheLoader和Callable callback
public class GuavaDemo {
public static void main(String args[]) throws Exception {
LoadingCache cache = CacheBuilder.newBuilder()
// 最大3个 //Cache中存储的对象,写入3秒后过期
.maximumSize(3).expireAfterWrite(3,
//记录命中率 失效通知
TimeUnit.SECONDS).recordStats().removalListener(new
RemovalListener
1、CacheLoader
在创建cache对象时,采用CacheLoader来获取数据,当缓存不存在时能够自动加载数据到缓存中
LoadingCache2、Callable Callbackcache = CacheBuilder.newBuilder() .maximumSize(3) .build( new CacheLoader () { @Override public String load(String s) throws Exception { return Constants.hm.get(s); } } );
public static Object get(String key,LoadingCache cache)throws Exception{
Object value=cache.get(key, new Callable() {
@Override
public Object call() throws Exception {
String v= Constants.hm.get(key);
//设置回缓存
cache.put(key,v);
return v;
}
});
return value;
}
三、缓存数据删除
GuavaCache的数据删除分为:被动删除和主动删除
1、被动删除- 基于数据大小的删除
LoadingCachecache= CacheBuilder.newBuilder() //最大个数 .maximumSize(3) .build(new CacheLoader () { //读取数据源 @Override public Object load(String key) throws Exception { return Constants.hm.get(key); } }); //读取缓存中的1的数据 缓存有就读取 没有就返回null System.out.println(cache.getIfPresent("5")); //读取4 读源并回写缓存 淘汰一个(LRU+FIFO) get("4",cache);
规则:LRU+FIFO
访问次数一样少的情况下,FIFO
- 基于过期时间的删除
隔多长时间后没有被访问过的key被删除
//缓存中的数据 如果3秒内没有访问则删除
.maximumSize(3).expireAfterAccess(3, TimeUnit.SECONDS)
。。。。
Thread.sleep(1000);
//访问1 1被访问
cache.getIfPresent("1");
//歇了2.1秒
Thread.sleep(2100);
//最后缓存中会留下1
System.out.println("==================================");
display(cache);
写入多长时间后过期
//等同于expire ttl 缓存中对象的生命周期就是3秒 .maximumSize(3).expireAfterWrite(3, TimeUnit.SECONDS) .build(new CacheLoader() { //读取数据源 @Override public Object load(String key) throws Exception { return Constants.hm.get(key); } }); display(cache); Thread.sleep(1000); //访问1 cache.getIfPresent("1"); //歇了2.1秒 Thread.sleep(2100); System.out.println("=================================="); display(cache);
- 基于引用的删除
可以通过weakKeys和weakValues方法指定Cache只保存对缓存记录key和value的弱引用。这样当没有其他强引用指向key和value时,key和value对象就会被垃圾回收器回收
LoadingCache2、主动删除cache = CacheBuilder.newBuilder() // 最大3个 值的弱引用 .maximumSize(3).weakValues() .build(); Object value = new Object(); cache.put("1",value); value = new Object();//原对象不再有强引用 //强制垃圾回收 System.gc(); System.out.println(cache.getIfPresent("1"));
- 单独删除
//将key=1 删除
cache.invalidate("1");
- 批量删除
//将key=1和2的删除
cache.invalidateAll(Arrays.asList("1","2"));
- 清空所有数据
//清空缓存 cache.invalidateAll();四、Guava Cache原理 1、GuavaCache核心原理之数据结构
Guava Cache的数据结构跟ConcurrentHashMap类似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。
相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。其数据结构图如下:
LocalCache为Guava Cache的核心类,包含一个Segment数组组成Segement数组的长度决定了cache的并发数每一个Segment使用了单独的锁,其实每个Segment继承了ReentrantLock,对Segment的写操作需要先拿到锁每个Segment由一个table和5个队列组成5个队列: Guava Cache提供了三种基本的缓存回收方式: 基于容量回收 除了以上三种还有主动删除,采用命令,上面已写过 GuavaCache构建的缓存不会"自动"执行清理和回收工作,也不会在某个缓存项过期后马上清理,也没有诸如此类的清理机制。 GuavaCache是在每次进行缓存操作的时候,惰性删除 如get()或者put()的时候,判断缓存是否过期 先通过key做hash定位到所在的Segment 通过位运算找首地址的偏移量 SegmentCount>=并发数且为2的n次方 再找到segment中的Entry链数组,通过key的hash定位到某个Entry节点
ReferenceQueue keyReferenceQueue : 已经被GC,需要内部清理的键引用队列
ReferenceQueue valueReferenceQueue : 已经被GC,需要内部清理的值引用队列
ConcurrentlinkedQueue
Queue
Queue
AtomicReferenceArray
ReferenceEntry是Guava Cache中对一个键值对节点的抽象,每个ReferenceEntry数组项都是一条ReferenceEntry链。并且一个ReferenceEntry包含key、hash、valueReference、next字段
(单链)
Guava Cache使用ReferenceEntry接口来封装一个键值对,而用ValueReference来封装Value值
2、GuavaCache核心原理之回收机制
在缓存项的数目达到限定值之前,采用LRU的回收方式定时回收
expireAfterAccess:缓存项在给定时间内没有被读/写访问,则回收。回收顺序和基于大小回收一样(LRU)
expireAfterWrite:缓存项在给定时间内没有被写访问(创建或覆盖),则回收基于引用回收
通过使用弱引用的键、或弱引用的值、或软引用的值,Guava Cache可以垃圾回收
V get(K key, CacheLoader super K, V> loader) throws ExecutionException {
// 注意,key不可为空
int hash = hash(checkNotNull(key));
// 通过hash定位到segment数组的某个Segment元素,然后调用其get方法
return segmentFor(hash).get(key, hash, loader);
}
V get(K key, int hash, CacheLoader super K, V> loader) throws
ExecutionException {
checkNotNull(key);
checkNotNull(loader);
try {
if (count != 0) { // read-volatile
// 内部也是通过找Entry链数组定位到某个Entry节点
ReferenceEntry



