WeakCache类的基本信息
类定义
// final➕default权限,符合最小权限原则(迪米特法则),仅为JDK代理定制化设计
final class WeakCache<K, P, V> {
// 如果类加载器被回收了,会通知到这个引用队列,回头就可以清除缓存
private final ReferenceQueue<K> refQueue = new ReferenceQueue<>();
// 总的缓存,下面原生注解的意思是,ConcurrentMap不支持null,
// 但他通过定义了一个叫null的Object对象来解决这个问题了
// 那为什么要解决这个问题呢?因为最顶层父加载器Bootstrap ClassLoader是c++实现的,
// 如果类加载器是null了,那他不是真的null,而是代表了Bootstrap ClassLoader。
// the key type is Object for supporting null key
private final ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map
= new ConcurrentHashMap<>();
// 反转缓存,便于快速查找是否这个值存在于二级缓存
private final ConcurrentMap<Supplier<V>, Boolean> reverseMap
= new ConcurrentHashMap<>();
// 如果计算第二级缓存的key
private final BiFunction<K, P, ?> subKeyFactory;
// 如何产生代理类对象
private final BiFunction<K, P, V> valueFactory;
/**
* Construct an instance of {@code WeakCache}
*
* @param subKeyFactory a function mapping a pair of
* {@code (key, parameter) -> sub-key}
* @param valueFactory a function mapping a pair of
* {@code (key, parameter) -> value}
* @throws NullPointerException if {@code subKeyFactory} or
* {@code valueFactory} is null.
*/
// 该构造函数在Proxy的weakCache属性定义处使用
public WeakCache(BiFunction<K, P, ?> subKeyFactory,
BiFunction<K, P, V> valueFactory) {
this.subKeyFactory = Objects.requireNonNull(subKeyFactory);
this.valueFactory = Objects.requireNonNull(valueFactory);
}
...
}
泛型参数说明
<K, P, V> 对应<ClassLoader,Class[],Class>,注意,进入正题前,先公布缓存内容的答案,弱引用对ClassLoader的封装对象是第一级key,弱引用对Class[]的封装是第二级key,Factory类对象(WeakCache类的私有内部类)是临时缓存值,CacheValue类对象(WeakCache类的私有静态内部类,弱引用且封装了生产的动态代理类)是最终缓存值。 其中K为类加载器不必多说,这个类加载器是加载目标对象类的类加载器,生成的代理类也会由该类加载器进行加载,原因很简单,类加载器实现了类之间的可见性,因此,需要使用同一层面的类加载器(由于双亲委派模型,子类加载器可见父类加载器,但反之不可见)。 P是一个类对象集合,其实稍作解释便可理解,这个类对象其实是目标对象实现的所有接口的类对象数组,相应的动态生成的代理类也会实现这些接口。 V是Class对象,即最终生产的代理对象类。 用来生产代理类对象的方法被封装在存在于java.lang.reflect.Proxy类中的ProxyClassFactory类中,没错,就是动态生成的代理类统一的父类Proxy类的内部类。
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>>
BiFunction是一个常见的函数式接口,提供ClassLoader和Class[](这两个上面已经分析类),返回Class,这个类对象就是动态生成的代理类对象,所以其实生成代理类的核心逻辑其实就被封装在了ProxyClassFactory实现了BiFunction接口的apply方法里。 本文暂时不详细学习和理解ProxyClassFactory对BiFunction接口的apply方法的实现逻辑,重点在WeakCache缓存的实现,来看get方法:
get方法分析
public V get(K key, P parameter) {
// K是类加载器,P是接口类对象数组,jdk代理是通过实现目标类实现的接口来实现的
// 所以这里要求接口类对象数组不能为空不过分吧。
Objects.requireNonNull(parameter);
// 清除缓存,现在只知道字面意思是这个,下面会具体分析源码(很简单)
expungeStaleEntries();
// 获取缓存的一级key,是通过类加载器和引用队列来计算
// 大白话就是返回一个弱引用(CacheKey是弱引用类的子类)封装的类加载器对象
// 这里会有一个疑问,因为CacheKey.valueOf方法会返回一个新对象,
// 这会导致,即使类加载器都是一个,但是返回的key都是新的弱引用封装对象,
// 那岂不是永远中不了缓存?答案就在CacheKey类重写了equal和hashCode。
// 最后把这个弱引用添加到私有变量refQueue引用队列的订阅里
// 以便回收的时候(上一行代码)可以从refQueue中得到要清除哪些key
Object cacheKey = CacheKey.valueOf(key, refQueue);
// 下面一行是原生注解,“懒加载(延迟加载)为特定的cacheKey安装二级valuesMap”
// lazily install the 2nd level valuesMap for the particular cacheKey
// 什么意思呢?字面理解valuesMap就是二级缓存Map
// 从上面可以推断出map(ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>>)
// 这个私有变量就是我们的总缓存,里面也肉眼可见的套了两层
// 那么valuesMap就是ConcurrentMap<Object, Supplier<V>>>了吧
ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey);
// 这里分析整个if代码块,如果第二层缓存是空
if (valuesMap == null) {
// 就开始创建。
ConcurrentMap<Object, Supplier<V>> oldValuesMap
= map.putIfAbsent(cacheKey,
valuesMap = new ConcurrentHashMap<>());
// 这里用在此判断旧的第二层缓存是否为空,
// 其实用到了乐观锁,这个代码块并没有做并发控制,
// 如果走到这发现oldValuesMap不为null,说明前一个执行到判断上一行代码的线程已经提前
// 给创建好了第二层缓存,这里用就是了,因为你不知道创建oldValuesMap的线程运行到哪了,
// 可能干了很多事。这个写法还是很值得学习的。
if (oldValuesMap != null) {
valuesMap = oldValuesMap;
}
}
// 下面是原生注解“创建subKey并从valuesMap中检索可能存在的由该subKey存储的Supplier<V>”
// create subKey and retrieve the possible Supplier<V> stored by that subKey from valuesMap
// 也就是说第二级缓存的key是通过弱引用封装的类加载器和接口类数组共同计算的,
// 从Proxy类对WeakCache的构造函数赋值可以看出,这个subKeyFactory是一个KeyFactory私有静态内部类,
// 和ProxyClassFactory类一样,同样实现了BiFunction接口,apply接口实现了生产第二级缓存key的逻辑
// 实际上,计算的过程仅与接口类数组的长度有关,和类加载器无关,
// 返回的第二级缓存key是同样是一个弱引用,弱引用的封装值是接口类数组。
// 事情突然有意思起来了,实际上二级缓存,最外层的缓存key是封装了类加载器的弱引用
// 第二级缓存的key是封装了接口类对象数组的弱引用,同样重写了equal和hashCode。
Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
// 最终获取到了Factory实例或者CacheValue实例
Supplier<V> supplier = valuesMap.get(subKey);
Factory factory = null;
// 下面循环还是乐观锁的并发控制
while (true) {
// 至少二番战才会到这
// 第一次是null;第二次是Factory对象;第三次是CacheValue对象
if (supplier != null) {
// supplier might be a Factory or a CacheValue<V> instance
// 如果是Factory的实例,则get方法内调用了
//(委托)ProxyClassFactory的apply方法来生产代理类对象,同时创建CacheValue类封装该对象,
// 并返回;如果是CacheValue对象,则直接返回封装的代理类对象。
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);
}
// 情况1:supplier为null,说明是第一次获取
if (supplier == null) {
supplier = valuesMap.putIfAbsent(subKey, factory);
if (supplier == null) {
// successfully installed Factory
supplier = factory;
}
// 如果putIfAbsent返回非null,说明其他线程已经放入了一个supplier
// 继续循环使用那个supplier
// else retry with winning supplier
}
// 情况2:supplier不为null,但可能已经失效
else {
// 这里campare and swap
if (valuesMap.replace(subKey, supplier, factory)) {
// successfully replaced
// cleared CacheEntry / unsuccessful Factory
// with our Factory
supplier = factory;
} else {
// retry with current supplier
// 替换失败,说明其他线程已经更新了supplier
// 获取最新的supplier
supplier = valuesMap.get(subKey);
}
}
}
}
expungeStaleEntries方法分析
值得注意的是expungeStaleEntries方法,其实就是在get前先看一下订阅了回收通知的引用队列里有没有值,有的话就去清除相应的缓存。
private void expungeStaleEntries() {
CacheKey<K> cacheKey;
while ((cacheKey = (CacheKey<K>)refQueue.poll()) != null) {
cacheKey.expungeFrom(map, reverseMap);
}
}
总结:
动态代理类的生成代价较高 每次调用 Proxy.newProxyInstance() 都会触发类的动态生成、定义和初始化。这个过程涉及字节码生成、类加载等操作,相对耗时。通过缓存已生成的代理类,可以显著提升性能。 相同接口和类加载器下代理类是可复用的 如果多次请求相同的接口组合和类加载器,生成的代理类结构是一样的,没有必要重复创建。缓存使得这些代理类可以被安全地复用。 防止内存泄漏与过度占用内存 使用 WeakCache 这种弱引用机制,可以确保即使类加载器不再使用,也不会导致内存泄漏。同时又能保持高频使用的代理类在缓存中存在。 支持并发访问 缓存机制内部使用了并发控制(如 ConcurrentMap 和 乐观锁的写法),保证多线程环境下高效、安全地获取代理类。
个人体会:
其实key的回收是很苛刻的,因为让类加载器做了弱引用的封装,类加载器如果被回收,说明加载的类对象都被回收了,因为每个类对象都持有对类加载器的强引用,进而类对象都被回收了,说明类的实例对象也被回收了,这里必有目标对象,从而目标对象都被回收了,说明代理对象也没有了意义。