阅读本篇文章前,请事先阅读 理解Java的强引用、软引用、弱引用和虚引用。 看看什么是强引用、什么是弱引用及它们的用途,很必要!!!

上一节讲到,获取对应的代理类时,首先会从缓存中去拿,若拿不到才会去生成。实现缓存的储存,如何根据指定值拿到缓存都是由WeakCache这个类实现的。

我们先去探究一下WeakCache~

一、WeakCache

WeakCache有两级缓存,它的键值对: (key, sub-key) -> value。 一级缓存的keys和values是弱引用, 二级缓存的sub-keys是强引用

sub-keys, 根据keys和parameters使用subKeyFactory(构造器传入)计算出的。 Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));

values, 跟获取sub-keys类似,但是它是使用valueFactory(构造器传入)。 value = Objects.requireNonNull(valueFactory.apply(key, parameter));

为啥要使用WeakCache作为动态代理的缓存,我在网上看到了一个文章,What is the use case of WeakCache in Java? [closed], 可以将里面的图片对象 类比 生成的代理类(都要占用较大的内存),也不知正确与否(JVM早日把你干掉!!!)

我认为的原因是,

  1. 生成的代理类占用内存较大,key(弱引用, GC时会被回收)失效时, 可以被及时处理(expungeStaleEntries()就是处理key失效时,清楚掉对应的value的方法,在getcontainsValue,size被调用时调用)

简而言之,为了能用到时随时能用到,但是不影响GC,毕竟内存很宝贵的

1. 变量与构造器


// 弱引用被回收时,将被添加到这个引用队列中
private final ReferenceQueue<K> refQueue
= new ReferenceQueue<>(); // the key type is Object for supporting null key
private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
= new ConcurrentHashMap<>(); // 保存value,当获取缓存的size时,就比较方便得到
private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
= new ConcurrentHashMap<>(); // 生成subKey及value的类
private final BiFunction<K, P, ?> subKeyFactory;
private final BiFunction<K, P, V> valueFactory;

构造器:

java.lang.reflect.WeakCache#WeakCache

    public WeakCache(BiFunction<K, P, ?> subKeyFactory,
BiFunction<K, P, V> valueFactory) {
this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
this.valueFactory = Objects.requireNonNull(valueFactory);
}

2. 重要的方法

2.1 get()!!!

结合java.lang.reflect.Proxy#getProxyClass0使用到的WeakCache.get方法,我们看看其get()的原理

    private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) { ...
// 从这开始
return proxyClassCache.get(loader, interfaces);
}

java.lang.reflect.Proxy#proxyClassCache


/**
* a cache of proxy classes
*/
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

java.lang.reflect.Proxy.KeyFactory,根据实现接口个数返回不同的key, 有兴趣的同学可以去看看。

java.lang.reflect.Proxy.ProxyClassFactory, 上节讲过的,若指定的参数的缓存失效,就会使用该工厂类,生成对应的代理类。

tips: Supplier, 是一个Java8提供的一个函数式接口,结果的提供者,其get方法会返回一个结果。

有了以上的知识,我们一起看看WeakCacheget实现吧

java.lang.reflect.WeakCache#get:

 public V get(K key, P parameter) {
Objects.requireNonNull(parameter); // 清理掉被GC的缓存
expungeStaleEntries(); // 将key包装成弱引用
Object cacheKey = CacheKey.valueOf(key, refQueue); // 通过cachekey获取二级缓存 sub-keys - values
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); // 验证二级缓存valuesMap是否为null, 为null就初始化;
// 还有情况可能就是在初始化过程中,其他线程已经将它初始化,若这样,将实例指向第一次初始化的实例
if (valuesMap == null) {
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
} // subKeyFactory = KeyFactory, 此时就是根据实现的接口个数返回不同的对象
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); // 第一次,valuesMap才被初始化,所以supplier为null
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null; // 下面是一个轮询,直到拿到不为null的supplier且supplier.get()不为null为止
while (true) {
if (supplier != null) {
// 第一次进来,supplier应该是Factory;
// 第二次,获取的对应的参数未变的话,从valuesMap中获取到的supplier就是一个CacheValue<V>的实例了,,此时就是从缓存中获取的了
// 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) // 懒加载
if (factory == null) {
factory = new Factory(key, parameter, subKey, valuesMap);
} if (supplier == null) {
//
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
// 一路成功的话,这里就会运行完毕,进行下一个循环,获取到值就直接返回value了
supplier = factory;
}
// supplier赋值的过程中,被其他线程提前赋值了, 继续循环
} else {
// 替换以前的supplier
if (valuesMap.replace(subKey, supplier, factory)) {
// successfully replaced
// cleared CacheEntry / unsuccessful Factory
// with our Factory
supplier = factory;
} else {
// 使用目前的supplier,重试
supplier = valuesMap.get(subKey);
}
}
}
}

我们去看看实现SuppilerFactory是如何提供返回结果的

java.lang.reflect.WeakCache.Factory

private final class Factory implements Supplier<V> {

        private final K key;
private final P parameter;
private final Object subKey;
private final ConcurrentMap<Object, Supplier<V>> valuesMap; Factory(K key, P parameter, Object subKey,
ConcurrentMap<Object, Supplier<V>> valuesMap) {
this.key = key;
this.parameter = parameter;
this.subKey = subKey;
this.valuesMap = valuesMap;
} @Override
public synchronized V get() { // serialize access
// re-check
Supplier<V> supplier = valuesMap.get(subKey);
if (supplier != this) {
// 在我们等待时发生了改变:
// 1. 被一个CacheValue替换了
// 2. were removed because of failure
// 此时,就返回null, 让WeakCache.get 继续循环
return null;
}
// else still us (supplier == this) // create new value
V value = null;
try { // !! 此时的valueFactory就是ProxyClassFactory; 这里会去生成代理类
value = Objects.requireNonNull(valueFactory.apply(key, parameter));
} finally {
if (value == null) { // remove us on failure
valuesMap.remove(subKey, this);
}
}
// the only path to reach here is with non-null value
assert value != null; // wrap value with CacheValue (WeakReference)
CacheValue<V> cacheValue = new CacheValue<>(value); // 将cacheValue保存起来
reverseMap.put(cacheValue, Boolean.TRUE); // 用cacheValue替换原有的值
// try replacing us with CacheValue (this should always succeed)
if (!valuesMap.replace(subKey, this, cacheValue)) {
throw new AssertionError("Should not reach here");
} // successfully replaced us with new CacheValue -> return the value
// wrapped by it
return value;
}
}

对于java.lang.reflect.WeakCache#get,java.lang.reflect.WeakCache.Factory#get源码的一个总结:

  1. valuesMap找不到subKey对应的supplier, 此时supplier是Factory的实例,调用supplier.get()的时候就去调用java.lang.reflect.WeakCache.Factory#get
  2. valuesMapsubKey对应的supplier,此时supplier就是CacheValue的实例

    我们来看看supplier是CacheValue是,调用supplier.get(),实际调用的哪

java.lang.reflect.WeakCache#get

按f7,step into ->

java.lang.ref.Reference#get

我们再来看看,CacheValue的结构

    private static final class CacheValue<V>
extends WeakReference<V> implements Value<V>
{
private final int hash; CacheValue(V value) {
// 点super进去看看,你就会知道了!!!
super(value);
this.hash = System.identityHashCode(value); // compare by identity
} ...
}

java.lang.reflect.WeakCache.Value

    private interface Value<V> extends Supplier<V> {}

我们没有看到CacheValue中实现Supplierget(), 但是CacheValue的父类的父类Reference早已提供了get()这个方法,返回代理类的对象。

难道SupplierReference是天作之合? 希望我慢慢欣赏代码的美~

二、总结

结合了java.lang.reflect.Proxy#getProxyClass0中使用到WeakCache的地方,讲了讲,缓存获取(get)的整个过程。

三、参考

  1. 理解Java的强引用、软引用、弱引用和虚引用
  2. What is the use case of WeakCache in Java? [closed]

设计模式(1-3)-动态代理(WeakCache的运用)的更多相关文章

  1. 设计模式之jdk动态代理模式、责任链模式-java实现

    设计模式之JDK动态代理模式.责任链模式 需求场景 当我们的代码中的类随着业务量的增大而不断增大仿佛没有尽头时,我们可以考虑使用动态代理设计模式,代理类的代码量被固定下来,不会随着业务量的增大而增大. ...

  2. Java设计模式系列之动态代理模式(转载)

    代理设计模式 定义:为其他对象提供一种代理以控制对这个对象的访问. 动态代理使用 java动态代理机制以巧妙的方式实现了代理模式的设计理念. 代理模式示例代码 public interface Sub ...

  3. 设计模式之Jdk动态代理

    什么是动态代理呢?就是在java的运行过程中,动态的生成的代理类.(为了更熟悉的了解动态代理,你必须先熟悉代理模式,可点击设计模式之代理模式 阅读)我们知道java属于解释型语言,是在运行过程中,寻找 ...

  4. Java设计模式之JDK动态代理原理

    动态代理核心源码实现public Object getProxy() { //jdk 动态代理的使用方式 return Proxy.newProxyInstance( this.getClass(). ...

  5. java设计模式中的动态代理

    Java的三种代理模式 1.代理模式 代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩 ...

  6. 设计模式学习——JAVA动态代理原理分析

    一.JDK动态代理执行过程 上一篇我们讲了JDK动态代理的简单使用,今天我们就来研究一下它的原理. 首先我们回忆下上一篇的代码: public class Main { public static v ...

  7. Android开发中无处不在的设计模式——动态代理模式

    继续更新设计模式系列.写这个模式的主要原因是近期看到了动态代理的代码. 先来回想一下前5个模式: - Android开发中无处不在的设计模式--单例模式 - Android开发中无处不在的设计模式-- ...

  8. 《Java设计模式》之代理模式 -Java动态代理(InvocationHandler) -简单实现

    如题 代理模式是对象的结构模式.代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用. 代理模式可细分为如下, 本文不做多余解释 远程代理 虚拟代理 缓冲代理 保护代理 借鉴文章 ht ...

  9. mybatis 插件的原理-责任链和动态代理的体现

    目录 1 拦截哪些方法 2 如何代理 3 代理对象 4 责任链设计模式 @ 如果没有自定义过拦截器, 可以看我前面的文章.如果不知道 JDK 动态代理怎么使用的, 可以看我这文章. 责任链设计模式理解 ...

随机推荐

  1. HCNP Routing&Switching之路由控制、路由策略和IP-Prefix List

    前文我们了解了IS-IS路由聚合和认证相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15306645.html:今天我们来聊一聊路由控制技术中的路由策 ...

  2. Linux系列(17) - >、>>的用法

    适用场景 输出重定向,将命令结果写入文件当中 差异化 >:覆盖原文件内容 >>:追加文件内容 格式 [命令] > [文件名]:将[命令]的结果覆盖到[文件名]该文件中,如果目录 ...

  3. Linux系列(7) - 链接命令

    硬链接 拥有相同的i节点和存储block块,可以看做事同一个文件 可通过i节点识别 不能跨分区 不能针对目录使用,只能针对文件 软链接 类似Windows快捷方式 软链接拥有自己的i节点和block块 ...

  4. 链式调用+对象属性与遍历+this指向+caller/callee

    之前的作业: 提示: 在开发的时候尽量在函数内部将作用都给调用好,在外部就能够直接使用 链式调用: 正常这样是不行的,因为没有具体返回值:  return 具体的对象,这样的才是链式操作,jquery ...

  5. python之jsonpath

    json 官方文档:http://docs.python.org/library/json.html JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它使 ...

  6. vivo全球商城-营销价格监控方案的探索

    一.背景 现在日常官网商城的运营中有一定概率出现以下两个问题: 1)优惠信息未对齐 官网商城促销优惠的类型越来越多,能影响最终用户实付价的优惠就有抢购.满减.优惠券.代金券等.实际业务操作中存在不同促 ...

  7. CF960G-Bandit Blues【第一类斯特林数,分治,NTT】

    正题 题目链接:https://www.luogu.com.cn/problem/CF960G 题目大意 求有多少个长度为\(n\)的排列,使得有\(A\)个前缀最大值和\(B\)个后缀最大值. \( ...

  8. P4338-[ZJOI2018]历史【LCT】

    正题 题目链接:https://www.luogu.com.cn/problem/P4338 题目大意 给出\(n\)个点的一棵树,和每个点进行\(access\)的次数\(a_i\),要求安排一个顺 ...

  9. 联表多字段update更新语句

    前言 最近需要写联表多字段update更新语句,发现不同的数据库,SQL语法也不一样,正好我这里有MySQL.Oracle.PgSQL三种数据库环境,分别练习.实操这三种数据库的联表update语句 ...

  10. NOIP 模拟六 考试总结

    T1辣鸡 T1就搞得这莫不愉快.. 大致题意是给你几个矩形,矩形覆盖的点都标记上,每个矩形无重复部分,求满足(x,y) (x+1,y+1)都标记过的点对数,范围1e9. 看起来很牛的样子,我确实也被1 ...