JDKダイナミックエージェントクラスの生成とキャッシュ

27803 ワード

原文のリンク:http://www.cnblogs.com/James-Gong/p/8126129.html
一、キャッシュ関連のクラスと主要な構造
プロキシ類の生成とキャッシュは主にjava.lang.reflect.WeakCacheというクラスで行われています.このようなプロキシキャッシュの主な構成は以下の通りです.
//   Reference      ,java gc        (      )
private final ReferenceQueue refQueue = new ReferenceQueue<>();
//            map,  key       ,      map
private final ConcurrentMap>> map = new ConcurrentHashMap<>();
//         CacheKey,         (      )
private final ConcurrentMap, Boolean> reverseMap = new ConcurrentHashMap<>();
//         (         key   ,            )
private final BiFunction subKeyFactory;
private final BiFunction valueFactory;
ここで一番核心となるのはキャッシュに使うmapです.ここでkey-value関係は以下の通りです.
フィールド
意味
コメント
key
一級キャッシュのkeyは、クラスキャリアのクラスリーダーによって決められます.
タイプ java.lang.reflect.WeakCache.Cachey.valueOf(K,ReferenceQue)
value
一級キャッシュvalueは、実際には二級キャッシュmapです.
タイプ java.util.co ncurrent.cn currentMap>
ソースはこのvalueの変数をvaluesMapと呼び、valuesMapは代理種類の二級キャッシュです.その中のkey-value関係は以下の通りです.
フィールド
意味
コメント
key
二級キャッシュキーは、クラスLoaderとinterfaces[]によってプロキシクラスを識別します.
タイプ java.lang.reflect.Proxy.KeyFactory.appley(Class Loader,Class>[])は、実際の値はObjectまたはKey 1またはKey 2またはKeyXであり、エージェントクラスによって実現されるインターフェースの数に依存します.
value
二級キャッシュvalue、つまり必要な代理クラスクラスクラスClass>)
タイプはjava.lang.reflect.WeakCache.Supplierで、実際のタイプはjava.lang.reflect.WeakCache.Factory.Factory(K,P,Object,ConcerentMap)が初めて記憶されています.その後取り出した時は、java.land.reflect.WeakCachect.Weak.Cchect.Weakcachect.Cchect.Vechect.Cect.Cect.Cace.Ver.Ver.Cace.Ver具体的な実現メカニズムについては後ほど紹介します.
二、具体的な実現
java.lang.reflect.Proxy類で代理類を生成する場合、クラスのnewProxyInstance方法を使用します.ソースは以下の通りです.
 @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        if (h == null) {
            throw new NullPointerException();
        }

        final Class>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
      //                 
        Class> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
          //              
            final Constructor> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
                // create proxy instance with doPrivilege as the proxy class may
                // implement non-public interfaces that requires a special permission
                return AccessController.doPrivileged(new PrivilegedAction() {
                    public Object run() {
                        return newInstance(cons, ih);
                    }
                });
            } else {
                return newInstance(cons, ih);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString());
        }
    }
      Class> cl = getProxyClass0(loader, intfs)   
getProxyClass0  java.lang.reflect.Proxy     ,    
    private static Class> getProxyClass0(ClassLoader loader,
                                           Class>... interfaces) {
      //       
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
      //         
        return proxyClassCache.get(loader, interfaces);
    }
ここの変数proxyClass CacheはProxy類の静的プライベートメンバ変数であり、そのタイプは上述のクラスjava.lang.reflect.WeakCacheである.
ここでは、初期化WeakCacheはProxyの2つの内部クラスjava.lang.reflectory.Proxy.KeyFactoryとjava.lang.reflectory.Proxy.ProxyClass Factoryを使用しています.
    /**
     * a cache of proxy classes
     */
    private static final WeakCache[], Class>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

java.lang.reflect.WeakCache.get(K, P) ,
 public V get(K key, P parameter) {
        Objects.requireNonNull(parameter);

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

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

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

        while (true) {
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue 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);
                }
            }
        }
上記操作
一級キャッシュを生成する時に、クラスjava.lang.reflect.Weak Cache.acheKeyの方法valueOf(K,ReferenceQue)を使って、その唯一性はクラス加載器によって決定されます.
二級キャッシュを生成する際に、Java.lang.reflect.WeakCacheのメンバー変数subKeyFactoryのapplyメソッドを使用しています.このメンバー変数は、実際のモデルはjava.lang.reflect.Proxy.KeyFactoryで、このappy方法のソースは以下の通りです.
      //             key
        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);
            }
        }
Key 1、Key 2、KeyXの下の実装は、ソースコードを見ることができますが、3つのカテゴリーは、java.lang.reflect.Proxyクラスの内部カテゴリです.key 0はObjectの対象です.
最後のステップでは、keyによって二級キャッシュを取得することが分かりにくいかもしれません.前に述べた二級キャッシュの値は、Java.lang.reflect.WeakCache.Factory.Factory(K,P,Object,ConcerentMap)として初めて記憶され、その後取り出した時は、java.lang.reflect.WeakCache.Weak Cachece.CacheValue.CacheValue.CleVue(V)です.ここではコード実行順序により具体的な実現を探究します.
 
1.キャッシュに必要なプロキシクラスが存在しない場合
サイクルに入る前にsupplier=null、factory=null、
第一ループの論理は
            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;
                }
            }
実行後、新規作成したfactoryを二級キャッシュに保存しました.valuesMap.get(subKey)= supplier=factory=new Factory(key、parameter、subKey、valuesMap)
第二循環の論理は
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue instance
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
ここで呼び出されたget方法はfactoryのget方法であり、get方法では、キャッシュのfactoryを本物のキャッシュエージェントクラスで置換します.
 public synchronized V get() { // serialize access
            // re-check 
           //         
            Supplier supplier = valuesMap.get(subKey);
            //supplier   supplier  ,     (       )
            if (supplier != this) {
                return null;
            }
            // else still us (supplier == this)

            // create new value
            V value = null;
            try {
          //      Class>
                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 cacheValue = new CacheValue<>(value);

            // try replacing us with CacheValue (this should always succeed)
           //            (      factory  cacheValue  factory)
            if (valuesMap.replace(subKey, this, cacheValue)) {
                // put also in reverseMap
                reverseMap.put(cacheValue, Boolean.TRUE);
            } else {
                throw new AssertionError("Should not reach here");
            }

            // successfully replaced us with new CacheValue -> return the value
            // wrapped by it
            return value;
        }
注意マルチスレッドの場合は以下のような特殊な場合があります.
スレッドが実行されると、最初のループが実行されます.cpuの使用権はスレッド二によって獲得される.この時点でスレッド二は、方法java.lang.reflect.WeakCache.get(K,P)に実行されたばかりである.keyによってセカンドキャッシュvalueを取得するステップは、実行後、2つのスレッドがすべてループに入り、2つのスレッドが同時にvaluesMap.get(subKey)=を満たす. supplier= ファクトリー= new Factory(key、parameter、subKey、valuesMap);そしてfactoryのget方法はスレッド同期です.
このとき、まずループに入るスレッドコードの実行ロジックは、上述の第二ループのロジックと同じです.後にループのスレッドに入るとコードの実行順序が異なります.
ループ中のコードロジック
       if (supplier != null) {
                // supplier might be a Factory or a CacheValue instance
                V value = supplier.get();    //       null
                if (value != null) {        // value == null,     return,        
                    return value;
                }
            }

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

factoryのget方法実行ロジック
            Supplier supplier = valuesMap.get(subKey);    // supplier cacheValue   
            if (supplier != this) {    // this  factory
                return null;
            }
2.キャッシュに必要なプロキシクラスがある場合.
一回のループを実行するだけで、ループ中のコードロジックは
            if (supplier != null) {
                // supplier might be a Factory or a CacheValue instance
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
ここのget方法はcacheValueのget方法であり、またjava.lang.reflect.WeakCache.CacheValue.acheValue.CValue(V)にはスーパークラスjava.lang.ref.Referenceにpublicのget方法が定義されているので、実際に呼び出されたのはこのスーパークラスのget方法で、実際に戻り、実際にエージェントを返します.
public abstract class Reference {
    private T referent;         /* Treated specially by GC */
    public T get() {
        return this.referent;
    }
} 
 
三、関連知識点
1.co.ncurrentMapのget、putIfAbsent、replaceの使用
2.java多形の関連特性:子類が父類を継承して一つのインターフェースを実現した時、父類とインターフェースの中に同名のpublic方法があれば、子類はインターフェースの中の方法を実現しなくてもいいです.直接父類の同名の方法を継承すればいいです.注意:インターフェースの中の方法は全部public abstractのです.だから、父の種類の中の方法はpublicの時の子の種類だけがインターフェースの中の方法を書き換える必要がありません.父の方法はpublicの子の種類ではありません.やはりインターフェースの中の方法を書き換える必要があります. 
 3.二重検査を行い、遅延初期化の競合条件「検査-初期化」を解決します.エージェントクラスを初めて生成するときは、遅延初期化の競合状態条件があります.ここでは、スレッドの安全を保証するために、プロキシクラスを最初に生成する際にスレッド同期が必要であり、後続のプロキシクラスを取得する場合は、マージ圧力を軽減するために必要ではないので、セカンダリキャッシュを生成する際にFactoryクラスを導入した.
本論文の参考:https://www.jianshu.com/p/9f5566b5e7fb
https://www.jianshu.com/p/2326a397802c
 
 
 
転載先:https://www.cnblogs.com/James-Gong/p/8126129.html