Design pattern (1-3) - dynamic agent (Application of WeakCache)

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,

  1. 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;


    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);


     * 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) {

        // Clean up the cache by GC
        // 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

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;

        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:

  1. 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
  2. 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()


Press f7, step into - >

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!!!
            this.hash = System.identityHashCode(value); // compare by identity


    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.

3, Reference

  1. Understand Java's strong reference, soft reference, weak reference and virtual reference
  2. What is the use case of WeakCache in Java? [closed]

Tags: Design Pattern

Posted on Tue, 02 Nov 2021 01:52:16 -0400 by asmon