ehcache3-源码简析三

ehcache3的evict策略是怎样的呢?从put操作可以一窥,这里以单层heap cache为例。

ehcache3的evict策略不可设置,只能通过eviction-advisor建议evict,但这种建议得不到保证且低效。ehcache3的evict策略其实是一种基于样本的LRU算法,即在全量数据中采集一定数量样本(默认为8),在样本集中选取lastAccessTime最小的进行evict。

 1 //put操作先存入元素,然后判断是否进行evict,有删减
 2 public PutStatus put(final K key, final V value) throws StoreAccessException {
 3
 4   checkKey(key);
 5   checkValue(value);
 6   final long now = timeSource.getTimeMillis();
 7
 8     //map.compute会进入ConcurrentHashMap遍历元素并计算value
 9   map.compute(key, new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() {
10     @Override
11     public OnHeapValueHolder<V> apply(K mappedKey, OnHeapValueHolder<V> mappedValue) {
12
13       if (mappedValue != null && mappedValue.isExpired(now, TimeUnit.MILLISECONDS)) {
14         updateUsageInBytesIfRequired(- mappedValue.size());
15         mappedValue = null;
16       }
17
18       if (mappedValue == null) {
19         OnHeapValueHolder<V> newValue = newCreateValueHolder(key, value, now, eventSink);
20         if (newValue != null) {
21           updateUsageInBytesIfRequired(newValue.size());
22           statOutcome.set(StoreOperationOutcomes.PutOutcome.PUT);
23         }
24         return newValue;
25       } else {
26         OnHeapValueHolder<V> newValue = newUpdateValueHolder(key, mappedValue, value, now, eventSink);
27         if (newValue != null) {
28           updateUsageInBytesIfRequired(newValue.size() - mappedValue.size());
29         } else {
30           updateUsageInBytesIfRequired(- mappedValue.size());
31         }
32         statOutcome.set(StoreOperationOutcomes.PutOutcome.REPLACED);
33         return newValue;
34       }
35     }
36   });
37
38     //enforceCapacity会判断是否进行evict操作。
39   enforceCapacity();
40
41 }

ConcurrentHashMap的compute操作

  1 public V compute(K key,
  2                  BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
  3   if (key == null || remappingFunction == null)
  4       throw new NullPointerException();
  5   int h = spread(key.hashCode());
  6   V val = null;
  7   int delta = 0;
  8   int binCount = 0;
  9   for (Node<K,V>[] tab = table;;) {
 10     Node<K,V> f; int n, i, fh;
 11     if (tab == null || (n = tab.length) == 0)
 12         tab = initTable();
 13     else if ((f = tabAt(tab, i = (n - 1) & h)) == null) {
 14       Node<K,V> r = new ReservationNode<K,V>();
 15       synchronized (r) {
 16         if (casTabAt(tab, i, null, r)) {
 17           binCount = 1;
 18           Node<K,V> node = null;
 19           try {
 20                 //桶内无元素,计算key的value,如果value非null则添加该key-value(Node);
 21                 //如果value为null则什么也不做
 22             if ((val = remappingFunction.apply(key, null)) != null) {
 23                 delta = 1;
 24                 node = new Node<K,V>(h, key, val, null);
 25             }
 26           } finally {
 27             setTabAt(tab, i, node);
 28           }
 29         }
 30       }
 31       if (binCount != 0)
 32         break;
 33     }
 34     else if ((fh = f.hash) == MOVED)
 35       tab = helpTransfer(tab, f);
 36     else {
 37       synchronized (f) {
 38         if (tabAt(tab, i) == f) {
 39           if (fh >= 0) {
 40             binCount = 1;
 41             for (Node<K,V> e = f, pred = null;; ++binCount) {
 42               K ek;
 43               if (e.hash == h &&
 44                   ((ek = e.key) == key ||
 45                    (ek != null && key.equals(ek)))) {
 46                 //桶内找到与key对应的node,计算value
 47                 val = remappingFunction.apply(key, e.val);
 48                 //如果value非null,则使用计算后的value替换旧的value
 49                 if (val != null)
 50                   e.val = val;
 51                 //如果value为null,则删除该node
 52                 else {
 53                   delta = -1;
 54                   Node<K,V> en = e.next;
 55                   if (pred != null)
 56                     pred.next = en;
 57                   else
 58                     setTabAt(tab, i, en);
 59                 }
 60                 break;
 61               }
 62               pred = e;
 63               //桶内没有找到key对应的node,根据key计算value,
 64               //如果value非null则将该key-value加入桶内,如果value为null则什么都不做
 65               if ((e = e.next) == null) {
 66                 val = remappingFunction.apply(key, null);
 67                 if (val != null) {
 68                   delta = 1;
 69                   pred.next =
 70                       new Node<K,V>(h, key, val, null);
 71                 }
 72                 break;
 73               }
 74             }
 75           }
 76           else if (f instanceof TreeBin) {
 77             binCount = 1;
 78             TreeBin<K,V> t = (TreeBin<K,V>)f;
 79             TreeNode<K,V> r, p;
 80             if ((r = t.root) != null)
 81               p = r.findTreeNode(h, key, null);
 82             else
 83               p = null;
 84             V pv = (p == null) ? null : p.val;
 85             val = remappingFunction.apply(key, pv);
 86             if (val != null) {
 87               if (p != null)
 88                 p.val = val;
 89               else {
 90                 delta = 1;
 91                 t.putTreeVal(h, key, val);
 92               }
 93             }
 94             else if (p != null) {
 95               delta = -1;
 96               if (t.removeTreeNode(p))
 97                 setTabAt(tab, i, untreeify(t.first));
 98             }
 99           }
100           }
101       }
102       if (binCount != 0) {
103         if (binCount >= TREEIFY_THRESHOLD)
104           treeifyBin(tab, i);
105         break;
106       }
107     }
108   }
109   if (delta != 0)
110     addCount((long)delta, binCount);
111   return val;
112 }

 1 protected void enforceCapacity() {
 2   StoreEventSink<K, V> eventSink = storeEventDispatcher.eventSink();
 3   try {
 4       //ATTEMPT_RATIO为4,即最多尝试evict4次,EVICTION_RATIO为2,即最多evict2个元素,capacity即我们设置的<heap unit="entries">x</heap>数,map.naturalSize()是当前已映射数
 5     for (int attempts = 0, evicted = 0; attempts < ATTEMPT_RATIO && evicted < EVICTION_RATIO
 6             && capacity < map.naturalSize(); attempts++) {
 7       //如果evict成功,evicted++
 8       if (evict(eventSink)) {
 9         evicted++;
10       }
11     }
12     storeEventDispatcher.releaseEventSink(eventSink);
13   } catch (RuntimeException re){
14     storeEventDispatcher.releaseEventSinkAfterFailure(eventSink, re);
15     throw re;
16   }
17 }
 1 boolean evict(final StoreEventSink<K, V> eventSink) {
 2   evictionObserver.begin();
 3   //产生的随机数用于确定首个样本的index
 4   final Random random = new Random();
 5
 6     //第一轮采样。
 7     //SAMPLE_SIZE为8,表示最少采样8个样本(如果样本不足就8个,采完就行),
 8     //EVICTION_PRIORITIZER是一个Comparator,会比较node的lastAccessTime,
 9     //EVICTION_ADVISOR即evict建议,可以自定义,
10     //第一轮采样会接收evict建议,如果第一轮年采样没evict的建议都是不evict,
11     //则进行第二轮采样,第二轮采样会忽略evict建议。
12     //注意,evictionAdvice在value存入时就已确定,即valueHolder中持有evictionAdvice(boolean)
13   Map.Entry<K, OnHeapValueHolder<V>> candidate = map.getEvictionCandidate(random, SAMPLE_SIZE, EVICTION_PRIORITIZER, EVICTION_ADVISOR);
14
15   if (candidate == null) {
16       //第二轮采样
17     // 2nd attempt without any advisor
18     candidate = map.getEvictionCandidate(random, SAMPLE_SIZE, EVICTION_PRIORITIZER, noAdvice());
19   }
20
21   if (candidate == null) {
22     return false;
23   } else {
24       //根据key删除元素
25     final Map.Entry<K, OnHeapValueHolder<V>> evictionCandidate = candidate;
26     final AtomicBoolean removed = new AtomicBoolean(false);
27     map.computeIfPresent(evictionCandidate.getKey(), new BiFunction<K, OnHeapValueHolder<V>, OnHeapValueHolder<V>>() {
28       @Override
29       public OnHeapValueHolder<V> apply(K mappedKey, OnHeapValueHolder<V> mappedValue) {
30         if (mappedValue.equals(evictionCandidate.getValue())) {
31           removed.set(true);
32           if (!(evictionCandidate.getValue() instanceof Fault)) {
33             eventSink.evicted(evictionCandidate.getKey(), evictionCandidate.getValue());
34             invalidationListener.onInvalidation(mappedKey, evictionCandidate.getValue());
35           }
36           updateUsageInBytesIfRequired(-mappedValue.size());
37           return null;//return null会删除
38         }
39         return mappedValue;
40       }
41     });
42     if (removed.get()) {
43       evictionObserver.end(StoreOperationOutcomes.EvictionOutcome.SUCCESS);
44       return true;
45     } else {
46       evictionObserver.end(StoreOperationOutcomes.EvictionOutcome.FAILURE);
47       return false;
48     }
49   }
50 }
 1 public Entry<K, V> getEvictionCandidate(Random rndm, int size, Comparator<? super V> prioritizer, EvictionAdvisor<? super K, ? super V> evictionAdvisor) {
 2   Node<K,V>[] tab = table;
 3   if (tab == null || size == 0) {
 4     return null;
 5   }
 6
 7   K maxKey = null;
 8   V maxValue = null;
 9
10   int n = tab.length;
11   int start = rndm.nextInt(n);
12
13   Traverser<K, V> t = new Traverser<K, V>(tab, n, start, n);
14   //advance()可以得到下一个node(下一个node有两种情况,1桶内,直接通过next得到,2桶内的next==null,则遍历下一个桶)
15   for (Node<K, V> p; (p = t.advance()) != null;) {
16     K key = p.key;
17     V val = p.val;
18     //adviseAgainstEviction即不建议evict
19     if (!evictionAdvisor.adviseAgainstEviction(key, val)) {
20         //通过prioritizer(Comparator)的比较,得到lastAccessTime最小的
21       if (maxKey == null || prioritizer.compare(val, maxValue) > 0) {
22         maxKey = key;
23         maxValue = val;
24       }
25       //虽然已经样本数已经达到要求,但是仍然继续遍历当前桶内节点(t.index==terminalIndex)
26       if (--size == 0) {
27         for (int terminalIndex = t.index; (p = t.advance()) != null && t.index == terminalIndex; ) {
28           key = p.key;
29           val = p.val;
30           if (!evictionAdvisor.adviseAgainstEviction(key, val) && prioritizer.compare(val, maxValue) > 0) {
31             maxKey = key;
32             maxValue = val;
33           }
34         }
35         return new MapEntry<K, V>(maxKey, maxValue, this);
36       }
37     }
38   }
39
40   return getEvictionCandidateWrap(tab, start, size, maxKey, maxValue, prioritizer, evictionAdvisor);
41 }
时间: 2024-10-13 23:38:27

ehcache3-源码简析三的相关文章

并发工具-CyclicBarrier源码简析

CyclicBarrier是循环栅栏的意思,循环的等待多个线程执行任务: <1> 示例代码如下: public class CyclicBarrierTest { public static CyclicBarrier cb = new CyclicBarrier(3, () -> System.out.println("-------开始点名-------")); public static void main(String[] args) { System.out

SpringMVC学习——概念、流程图、源码简析(一)

学习资料:开涛的<跟我学SpringMVC.pdf> 众所周知,springMVC是比较常用的web框架,通常整合spring使用.这里抛开spring,单纯的对springMVC做一下总结. 概念 HandlerMapping:处理器映射,对请求的URL进行映射为具体的处理器(如果有拦截器也包含拦截器,会将Handler和多个HandlerInterceptor封装为HandlerExecutionChain对象) HandlerAdapter:处理器适配器,适配不同类型的处理器,如Cont

0002 - Spring MVC 拦截器源码简析:拦截器加载与执行

1.概述 Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理.例如通过拦截器可以进行权限验证.记录请求信息的日志.判断用户是否登录等. 2.简单示例 2.1.继承 HandlerInterceptorAdapter 抽象类实现一个拦截器.代码如下: public class DemoInterceptor extends HandlerInterceptorAdapter { @Override    pu

JDK源码简析--java.lang包中的基础类库

题记 JDK,Java Development Kit. 我们必须先认识到,JDK只是,仅仅是一套Java基础类库而已,是Sun公司开发的基础类库,仅此而已,JDK本身和我们自行书写总结的类库,从技术含量来说,还是在一个层级上,它们都是需要被编译成字节码,在JRE中运行的,JDK编译后的结果就是jre/lib下得rt.jar,我们学习使用它的目的是加深对Java的理解,提高我们的Java编码水平. 本系列所有文章基于的JDK版本都是1.7.16. 本节内容 在本节中,简析java.lang包所包

JDK源码简析--java.util包中的工具类库

题记 JDK,Java Development Kit. 我们必须先认识到,JDK只是,仅仅是一套Java基础类库而已,是Sun公司开发的基础类库,仅此而已,JDK本身和我们自行书写总结的类库,从技术含量来说,还是在一个层级上,它们都是需要被编译成字节码,在JRE中运行的,JDK编译后的结果就是jre/lib下得rt.jar,我们学习使用它的目的是加深对Java的理解,提高我们的Java编码水平. 本系列所有文章基于的JDK版本都是1.7.16. 本节内容 在本节中,简析java.util包所包

Linux Hugetlbfs内核源码简析-----(一)Hugetlbfs初始化

一.引言 为了实现虚拟内存管理机制,操作系统对内存实行分页管理.自内存“分页机制”提出之始,内存页面的默认大小便被设置为 4096 字节(4KB),虽然原则上内存页面大小是可配置的,但绝大多数的操作系统实现中仍然采用默认的 4KB 页面.当某些应用的需要使用的内存达到几G.甚至几十G的时候,4KB的内存页面将严重制约程序的性能. CPU缓存中有一组缓存专门用于缓存TLB,但其大小是有限的.当采用的默认页面大小为 4KB,其产生的TLB较大,因而将会产生较多 TLB Miss 和缺页中断,从而大大

[tomcat]源码简析 异步/非阻塞和请求构成

提出疑惑 SpringFramework5.0又新增加了一个功能Webflux(响应式编程),是一个典型非阻塞异步的框架.我们知道servlet3.0实现异步(AsyncContext),servlet3.1又提出了非阻塞IO.对此我一直有两点疑惑:1.tomcat8底层已经默认使用NIO了,不是已经是IO非阻塞了吗,怎么又说servlet3.1解决了非阻塞.2.关于异步,如果开发者在serlvet中开一个业务线程来实现,也算异步,为什么3.0还提供了一个组件来解决,那么这种方式和开发者自己开个

第五节:JQuery框架源码简析(1)

(转自老惠的博客) JQuery是一个应用广泛.非常优秀的JavaScript框架,其代码简洁而优雅,有很多值得我们学习的地方.这里仅仅对其代码结构做一个简单的分析,方便大家的理解和学习. 我们进行分析.分解的基准版本是jQuery1.7.1. 开始之前,请准备好以下素材和工具: jQuery源代码:jquery-1.7.1.js 文本编辑器:EditPlus,或者你喜欢的 参考书:<jQuery高级编程>.<jQuery技术内幕:深入解析jQuery架构设计与实现原理>.<

Android -- Camera源码简析,启动流程

com.android.camera.Camera.java,主要的实现Activity,继承于ActivityBase. ActivityBase 在ActivityBase中执行流程: onCreate中进行判断是否是平板: onResume中判断是否锁屏,锁屏&camera不存在时候,mOnResumePending置为true,否则置为false并执行doOnResume: onWindowFocusChanged中判断是否获取到焦点&mOnResumePending,满足的话执行