代理4 动态代理的缓存机制

1.背景

上一节大致介绍了Proxy动态代理的原理,从几个疑问上面分析,这一节介绍一下动态代理的缓存机制。网上的资源比较少。
可以怀着下面几个问题阅读源码

为什么要缓存
缓存的内容是什么
哪里调用的缓存
缓存的实现机制
缓存的过期机制

2.属性说明

Proxy涉及的缓存主要在java.lang.reflect.WeakCache这个类中
该类存在于jdk反射的包中
当生成代理对象的时候会用到

java.lang.reflect.Proxy#newProxyInstance
java.lang.reflect.Proxy#getProxyClass0
java.lang.reflect.WeakCache#get

看这个类的结构

//用了Reference记录引用队列,java gc时配合清除缓存用
private final ReferenceQueue<K> refQueue  = new ReferenceQueue<>();
//缓存的底层实现
private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();
//记录所有缓存中的CacheKey,配合缓存的过期机制
private final ConcurrentMap<Supplier<V>, Boolean> reverseMap = new ConcurrentHashMap<>();
//两个二元操作函数
private final BiFunction<K, P, ?> subKeyFactory;
private final BiFunction<K, P, V> valueFactory;

这里最重要的的是map,将key-value介绍如下

字段 意义 备注
key 通过classLoader来进行一级缓存 实际类型为CacheKey
value 二级缓存 后续会介绍

源码中把这个value的变量称为valuesMap,这里顺便借用,介绍这个“二级缓存”是干嘛的

字段 意义 备注
key 二级缓存的key,由classLoader和interfaces[]标识代理类 实际类型在java.lang.reflect.Proxy.KeyFactory#apply,为Key1或者Key2或者KeyX或者object,取决于代理类实现的接口数量
value 用于返回代理类(即Class<?>) 第一次存储实际类型为java.lang.reflect.WeakCache.Factory#Factory,之后取出时,都是java.lang.reflect.WeakCache.CacheValue

那么为什么要用缓存,缓存的内容是什么
缓存就是在动态代理生成代理类时,只用生成一次后面尽量直接复用

3.实现机制

java.lang.reflect.WeakCache#get

public V get(K key, P parameter) {
        Objects.requireNonNull(parameter);

        expungeStaleEntries();

        Object cacheKey = CacheKey.valueOf(key, refQueue);

        // lazily install the 2nd level valuesMap for the particular cacheKey
        ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
        if (valuesMap == null) {
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }

        // create subKey and retrieve the possible Supplier<V> stored by that
        // subKey from valuesMap
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;

        while (true) {
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue<V> instance
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            // else no supplier in cache
            // or a supplier that returned null (could be a cleared CacheValue
            // or a Factory that wasn't successful in installing the CacheValue)

            // lazily construct a Factory
            if (factory == null) {
                factory = new Factory(key, parameter, subKey, valuesMap);
            }

            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    // successfully installed Factory
                    supplier = factory;
                }
                // else retry with winning supplier
            } else {
                if (valuesMap.replace(subKey, supplier, factory)) {
                    // successfully replaced
                    // cleared CacheEntry / unsuccessful Factory
                    // with our Factory
                    supplier = factory;
                } else {
                    // retry with current supplier
                    supplier = valuesMap.get(subKey);
                }
            }
        }
    }

注意下面几点

3.1 ConcurrentMap的get以及putIfAbsent的使用

可以看到有几处代码,形如

ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
        if (valuesMap == null) {
            ConcurrentMap<Object, Supplier<V>> oldValuesMap
                = map.putIfAbsent(cacheKey,
                                  valuesMap = new ConcurrentHashMap<>());
            if (oldValuesMap != null) {
                valuesMap = oldValuesMap;
            }
        }

这个是为了保证在多线程下,去获取同一个key,拿到的是同一个引用对象
可以理解像单例模式里面要判断两次,来保证不会多次生成对象

if(instance==null) {
  synchronize(XXX.class) {
    if(instance==null){
      instance = new XXX();
    }
  }
}

可以参考http://wxl24life.iteye.com/blog/1746794

3.2 为什么说第一次valuesMap里面value的实际类型和之后不一样

根据Supplier<V> supplier = valuesMap.get(subKey);中supplier值以及在while循环第几次,按照时间先后考虑

3.2.1 supplier==null,第一次while循环

这种出现在第一次请求中,二级缓存没有该key

逻辑为

supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
    // successfully installed Factory
    supplier = factory;
}

此时valuesMap里面的key是subKey
subKey是

Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));

根据java.lang.reflect.Proxy.KeyFactory#apply
subKey实际类型是Key1,Key2,Object或者KeyX,根据实现的接口数量决定

private static final class KeyFactory
        implements BiFunction<ClassLoader, Class<?>[], Object>
    {
        @Override
        public Object apply(ClassLoader classLoader, Class<?>[] interfaces) {
            switch (interfaces.length) {
                case 1: return new Key1(interfaces[0]); // the most frequent
                case 2: return new Key2(interfaces[0], interfaces[1]);
                case 0: return key0;
                default: return new KeyX(interfaces);
            }
        }
    }

3.2.2 supplier==null,第二次while循环(或者因其他原因,第n次while循环)

这种出现在第一次请求中,二级缓存没有该key,supplier被赋值之后
逻辑为

if (supplier != null) {
  // supplier might be a Factory or a CacheValue<V> instance
  V value = supplier.get();
  if (value != null) {
     return value;
  }
 }

supplier在第一次循环时,已经为factory了,这里调用get方法,就是进入
java.lang.reflect.WeakCache.Factory#get的逻辑

        @Override
        public synchronized V get() { // serialize access
            // re-check
            Supplier<V> supplier = valuesMap.get(subKey);
            if (supplier != this) {
                //supplier和当前supplier不等,验证不正确
                return null;
            }
            V value = null;
            try {
                //生成代理类对应的Class<?>信息
                value = Objects.requireNonNull(valueFactory.apply(key, parameter));
            } finally {
                if (value == null) { // remove us on failure
                    valuesMap.remove(subKey, this);
                }
            }
            assert value != null;
            CacheValue<V> cacheValue = new CacheValue<>(value);

            //把之前二级缓存的value替换为当前的CacheValue
            if (valuesMap.replace(subKey, this, cacheValue)) {
                // put also in reverseMap
                reverseMap.put(cacheValue, Boolean.TRUE);
            } else {
                throw new AssertionError("Should not reach here");
            }
            return value;
        }

其中, value = Objects.requireNonNull(valueFactory.apply(key, parameter));就是代理类

java.lang.reflect.Proxy.ProxyClassFactory#apply //返回Class<?>
//具体生成细节参考上一节

在生成value之后

//把当前的值即this(类型为Factory)替换为了cacheValue(类型CacheValue)
valuesMap.replace(subKey, this, cacheValue)

并且这一次get()最终返回的是value;也就是Class<?>

3.2.3 supplier!=null

出现在之后的某一次请求中,二级缓存已经有该key-value

if (supplier != null) {
  // supplier might be a Factory or a CacheValue<V> instance
  V value = supplier.get();
  if (value != null) {
     return value;
  }
 }

此时,supplier的类型为CacheValue
在Factory#get中生成时,就是
CacheValue<V> cacheValue = new CacheValue<>(value);
CacheValue#get方法,最终转入java.lang.ref.Reference#get,返回的就是这个value,即构造函数的参数value

3.3缓存的过期机制

这里用了java.lang.reflect.WeakCache.CacheKey是继承WeakReference的
即弱引用,在java gc中,满足适当的条件时,当一个referent是ROOT可达且为弱引用时,会将其放入对应的referenceQueue。
核心函数在

private void expungeStaleEntries() {
        CacheKey<K> cacheKey;
        while ((cacheKey = (CacheKey<K>)refQueue.poll()) != null) {
            cacheKey.expungeFrom(map, reverseMap);
        }
    }

可能会奇怪这里并没有refQueue的enqueue操作,这个操作是java gc的时候做的,这里不深究。
只要知道gc时,某些场景会进行refQueue的enqueue即可。
那么,之后再调用expungeStaleEntries()就能清除过期的缓存。

4. 思考

4.1 为什么要二级缓存

一级缓存用来区分classLoader,二级缓存用来区分实现的接口
生成类的缓存是按照ClassLoader来划分的,这是因为类的区分不仅根据类名还根据装载类的ClassLoader,也就是说同一个类被不同的ClassLoader加载,那么它们也是不同的.
参考
http://www.cnblogs.com/cruze/p/3843996.html
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.3

4.2 ConcurrentHashMap想要获取SingleValue的方法(严格来说是同一个引用)

如果putIfAbsent返回值不为null,那么返回该值,不要重新put
http://wxl24life.iteye.com/blog/1746794

4.3 二级缓存valueMaps的value的类型不一样

第一次是Factory,调用get()时,生成代理类信息之后,将CacheValue作为值替换了当前key对应的value
之后调用,都是CacheValue.get()返回java.lang.ref.Reference#get,最终就是CacheValue的构造函数参数,即代理类信息Class<?>

4.4 一级缓存的key-value与二级缓存的key-value

4.5 在'背景'中提出的问题,在上文中都有解释

4.6 缓存的过期机制

利用java.lang.reflect.WeakCache.CacheKey继承WeakReference,配合ReferenceQueue使用。
在java gc时某些条件下触发。

5.暂时不明白的问题

5.1 reverseMap的value有什么用?

感觉纯粹是利用ConcurrentHashMap的"Concurrent"的特性,value并没有什么用,代码里面并没有处理里面的value,都是Boolean.TRUE;

5.2 实现二级缓存的逻辑为什么要这么写,不能一步到位吗?

为什么先放入Factory,调用Factory.get时进行replace操作再放入CacheValue,不能一步到位写入CacheValue后面直接用吗

6. refer

concurrentHashMap 的putIfAbsent完成single value
http://wxl24life.iteye.com/blog/1746794

cglib的缓存说明
http://www.cnblogs.com/cruze/p/3843996.html
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.3

gc 与 reference
http://www.cnblogs.com/maxmys/p/3998842.html
http://hongjiang.info/java-referencequeue/
http://zhang-xzhi-xjtu.iteye.com/blog/413159

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • /Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home...
    光剑书架上的书阅读 3,868评论 2 8
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,637评论 18 139
  • 一. Java基础部分.................................................
    wy_sure阅读 3,805评论 0 11
  • 2017年4月19日 (下午11:10) 一大早7:30从家里出发,10:40的火车离开北...
    红梅_7a37阅读 3,076评论 3 4