JDKダイナミックエージェントクラスの生成とキャッシュ
27803 ワード
原文のリンク:http://www.cnblogs.com/James-Gong/p/8126129.html
一、キャッシュ関連のクラスと主要な構造
プロキシ類の生成とキャッシュは主にjava.lang.reflect.WeakCacheというクラスで行われています.このようなプロキシキャッシュの主な構成は以下の通りです.
フィールド
意味
コメント
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方法を使用します.ソースは以下の通りです.
ここでは、初期化WeakCacheはProxyの2つの内部クラスjava.lang.reflectory.Proxy.KeyFactoryとjava.lang.reflectory.Proxy.ProxyClass Factoryを使用しています.
一級キャッシュを生成する時に、クラスjava.lang.reflect.Weak Cache.acheKeyの方法valueOf(K,ReferenceQue)を使って、その唯一性はクラス加載器によって決定されます.
二級キャッシュを生成する際に、Java.lang.reflect.WeakCacheのメンバー変数subKeyFactoryのapplyメソッドを使用しています.このメンバー変数は、実際のモデルはjava.lang.reflect.Proxy.KeyFactoryで、このappy方法のソースは以下の通りです.
最後のステップでは、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、
第一ループの論理は
第二循環の論理は
スレッドが実行されると、最初のループが実行されます.cpuの使用権はスレッド二によって獲得される.この時点でスレッド二は、方法java.lang.reflect.WeakCache.get(K,P)に実行されたばかりである.keyによってセカンドキャッシュvalueを取得するステップは、実行後、2つのスレッドがすべてループに入り、2つのスレッドが同時にvaluesMap.get(subKey)=を満たす. supplier= ファクトリー= new Factory(key、parameter、subKey、valuesMap);そしてfactoryのget方法はスレッド同期です.
このとき、まずループに入るスレッドコードの実行ロジックは、上述の第二ループのロジックと同じです.後にループのスレッドに入るとコードの実行順序が異なります.
ループ中のコードロジック
一回のループを実行するだけで、ループ中のコードロジックは
三、関連知識点
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
一、キャッシュ関連のクラスと主要な構造
プロキシ類の生成とキャッシュは主にjava.lang.reflect.WeakCacheというクラスで行われています.このようなプロキシキャッシュの主な構成は以下の通りです.
// Reference ,java gc ( )
private final ReferenceQueue refQueue = new ReferenceQueue<>();
// map, key , map
private final ConcurrentMap
ここで一番核心となるのはキャッシュに使う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
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
上記操作一級キャッシュを生成する時に、クラス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