Please read this article before reading it Understand Java's strong reference, soft reference, weak reference and virtual reference . It is necessary to see what is strong reference, what is weak reference and their purpose!!!
Previous section When you get the corresponding proxy class, you will first get it from the cache. If you can't get it, you will generate it. The class WeakCache implements the storage of cache and how to get the cache according to the specified value.
Let's explore WeakCache first~
1, WeakCache
WeakCache has two levels of cache. Its key value pairs are: (key, sub key) - > value. The keys and values of the L1 cache are weak references, and the sub keys of the L2 cache are strong references.
Sub keys, calculated using subkeyfactory (passed in by constructor) according to keys and parameters. Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
values is similar to getting sub keys, but it uses valuefactory (passed in by constructor). value = Objects.requireNonNull(valueFactory.apply(key, parameter));
Why use WeakCache as a dynamic proxy cache? I saw an article on the Internet, What is the use case of WeakCache in Java? [closed] , you can compare the image objects inside to the generated proxy class (both occupy a large amount of memory), and you don't know whether it is correct or not (the JVM will kill you as soon as possible!!!)
I think the reason is,
- The generated proxy class takes up a large amount of memory. When the key (weak reference, which will be recycled during GC) fails, it can be processed in time (expungeStaleEntries() is the method to clear the corresponding value when the key fails, which is called when get, containsvalue and size are called)
In short, in order to be available at any time, but without affecting GC, after all, memory is very valuable
1. Variables and constructors
// When a weak reference is recycled, it is added to the reference queue 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<>(); // Save value. It is more convenient to get the size of the cache private final ConcurrentMap<Supplier<V>, Boolean> reverseMap = new ConcurrentHashMap<>(); // Generate class of subKey and value private final BiFunction<K, P, ?> subKeyFactory; private final BiFunction<K, P, V> valueFactory;
Constructor:
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. Important methods
2.1 get()!!!
Combined with the WeakCache.get method used by java.lang.reflect.Proxy#getProxyClass0, let's see the principle of get()
private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) { ... // From here on 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 returns different keys according to the number of implementation interfaces. Interested students can go and have a look.
java.lang.reflect.Proxy.ProxyClassFactory. As mentioned in the previous section, if the cache of the specified parameters fails, the factory class will be used to generate the corresponding proxy class.
tips: Supplier is a functional interface provided by Java 8. It is a result provider whose get method will return a result.
With the above knowledge, let's take a look at the get implementation of WeakCache
java.lang.reflect.WeakCache#get: 👇
public V get(K key, P parameter) { Objects.requireNonNull(parameter); // Clean up the cache by GC expungeStaleEntries(); // Wrap the key as a weak reference Object cacheKey = CacheKey.valueOf(key, refQueue); // Obtain L2 cache sub keys - values through cachekey ConcurrentMap<Object, Supplier<V>> valuesMap = map.get(cacheKey); // Verify whether the L2 cache valuesMap is null, and initialize if it is null; // Another possibility is that other threads have initialized it during initialization. If so, point the instance to the instance initialized for the first time if (valuesMap == null) { ConcurrentMap<Object, Supplier<V>> oldValuesMap = map.putIfAbsent(cacheKey, valuesMap = new ConcurrentHashMap<>()); if (oldValuesMap != null) { valuesMap = oldValuesMap; } } // subKeyFactory = KeyFactory, which returns different objects according to the number of implemented interfaces Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); // The first time, valuesMap is initialized, so the supplier is null Supplier<V> supplier = valuesMap.get(subKey); Factory factory = null; // The following is a poll until a non null supplier is obtained and supplier.get() is not null while (true) { if (supplier != null) { // For the first time, the supplier should be Factory; // The second time, if the corresponding parameters obtained remain unchanged, the supplier obtained from valuesMap is an instance of cachevalue < V > and is obtained from the cache at this time // supplier might be a Factory or a CacheValue<V> instance V value = supplier.get(); if (value != null) { return value; } } // Reason for executing the following code: // 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) // Lazy loading if (factory == null) { factory = new Factory(key, parameter, subKey, valuesMap); } if (supplier == null) { // supplier = valuesMap.putIfAbsent(subKey, factory); if (supplier == null) { // If all the way is successful, the operation will be completed here, the next cycle will be carried out, and the value will be returned directly when the value is obtained supplier = factory; } // In the process of supplier assignment, it is assigned by other threads in advance, and the loop continues } else { // Replace the previous supplier if (valuesMap.replace(subKey, supplier, factory)) { // successfully replaced // cleared CacheEntry / unsuccessful Factory // with our Factory supplier = factory; } else { // If the replacement fails, use the current supplier and try again supplier = valuesMap.get(subKey); } } } }
Let's take a look at how the Factory implementing the supplier provides the returned results
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) { // As we waited, something changed: // 1. Replaced by a CacheValue // 2. were removed because of failure // At this point, return null and let WeakCache.get continue the loop return null; } // else still us (supplier == this) // create new value V value = null; try { // !! In this case, the valueFactory is ProxyClassFactory; The proxy class will be generated here 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); // Save cacheValue reverseMap.put(cacheValue, Boolean.TRUE); // Replace the original value with 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; } }
For java.lang.reflect.weakcache #get, a summary of java.lang.reflect.weakcache.factory #get source code:
- If valuesMap cannot find the supplier corresponding to the subKey, then the supplier is an instance of Factory. When calling supplier.get(), call java.lang.reflect.WeakCache.Factory#get
- If valuesMap has a supplier corresponding to a subKey, the supplier is an instance of CacheValue
Let's take a look at the CacheValue of the supplier, which is actually called by calling supplier.get()
java.lang.reflect.WeakCache#get
Press f7, step into - >
java.lang.ref.Reference#get
Let's take a look at the structure of CacheValue
private static final class CacheValue<V> extends WeakReference<V> implements Value<V> { private final int hash; CacheValue(V value) { // Click super to go in and have a look, you will know!!! super(value); this.hash = System.identityHashCode(value); // compare by identity } ... }
java.lang.reflect.WeakCache.Value
private interface Value<V> extends Supplier<V> {}
We don't see the get() of the Supplier implemented in CacheValue, but the parent class Reference of the parent class of CacheValue has already provided the get() method to return the object of the proxy class.
Is Supplier and Reference a match made in heaven? I hope I slowly appreciate the beauty of the code~
2, Summary
It combines the places where WeakCache is used in java.lang.reflect.Proxy#getProxyClass0, and describes the whole process of cache get.