JVMはJava反射をどのように実現していますか?
7456 ワード
Javaの反射アプリケーションシーンは非常に多く、IDEが連想をするとき、SpringのIoCコンテナなどがあります.反射によって、他のオブジェクトのプライベートメソッドを呼び出したり、任意の属性を取得したりするなど、普段できないことをすることができます.つまり、反射の前では、Javaオブジェクトにプライバシーがありません.
反射呼び出しの実装
まず
ここで、方法の呼び出しは、
上のコードの実行結果は次のとおりです.
上記の結果が印刷された呼び出しスタックでは、注釈の3ステップ目の4ステップ目に、先に使用された委任実装が表示され、委任実装がローカル実装を委任していることがわかります.
それでは問題が来ました!最終的にローカルで実装する以上、なぜ中間に委任実装を追加するのか.では、下を見続けます.
これは,以上の2つの他に動的実装があるためであり,委任実装とは,ローカル実装と動的実装との間を切り替えるためにすぎない.動的実装はバイトコード技術である.
しかし、一度だけ呼び出されると、動的実装は、動的実装の動作バイトコードが遅いため、ローカル実装は動的実装ブロックよりも少し遅い.JVMは、メソッド呼び出しが毎回少ない(または1回しか呼び出さない)と判断するので、16を設定し、15以上呼び出された場合、つまり17回目から動的実装を使用します.Java->c+->Javaのプロセスを経なければならないため、ローカル実装を使用します.17回目から、JVMはすでに生成されたバイトコードを利用してメソッド呼び出しを行うので、これは間違いなく増加します!
上のコードを20回繰り返して、直感的に見てみましょう.
次に、実行結果を示します.
結果から、「バージョン17」からダイナミックインプリメンテーションが使用されていることがわかります.
呼び出しのオーバーヘッドを反射
さっきのコードの中でClassforNameはローカルメソッドを呼び出し、getMethodはすべての共有メソッドを巡回し、見つからない場合は親に検索します.これらの操作は時間がかかります.上のコードを改造して1億回実行すれば、私のパソコンの結果は550 msです.直接呼び出すには5 msしかかかりません.格差は非常に大きいことがわかるが,他のプロセスの影響,メソッドのインライン,Intergerのデータキャッシュなど,実験は比較的粗末である.次の改造後のローカルコード:
反射呼び出しの実装
まず
Method.invoke()
の方法を観察してみましょう.関連するソースコードは、java.lang.reflect
パケットのMethod
クラスにある.public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
// , ...
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
// ...
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
ここで、方法の呼び出しは、
MethodAccessor
タイプのma
オブジェクトに委任されて処理されることがわかる.これはインタフェースで、2つの実装クラスがあります.1つの委任インプリメンテーション(DelegatingMethodAccessorImpl
)、1つのローカルインプリメンテーション(NativeMethodAccessorImpl
).各Methodインスタンスの最初の呼び出しは、委任実装を使用します!,しかし、委任による最終的な委任の実現は確かにローカルに実現される.次の例を見てみましょう./**
* @author gzd
* @date 2018-09-09 5:59
* @desc
*/
public class HowReflect {
public static void targetMethod(int i) {
//
new Exception("version " + i)
.printStackTrace();
}
public static void main(String[] args) throws Exception {
Class> howReflect = Class.forName("com.ynwa.jvm.HowReflect");
Method method = howReflect.getMethod("targetMethod", int.class);
//
method.invoke(null, 0);
}
}
上のコードの実行結果は次のとおりです.
java.lang.Exception: 0
at com.ynwa.jvm.HowReflect.targetMethod(HowReflect.java:13)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
// 4
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
// 3
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
// 2
at java.lang.reflect.Method.invoke(Method.java:498)
// 1 main
at com.ynwa.jvm.HowReflect.main(HowReflect.java:21)
上記の結果が印刷された呼び出しスタックでは、注釈の3ステップ目の4ステップ目に、先に使用された委任実装が表示され、委任実装がローカル実装を委任していることがわかります.
それでは問題が来ました!最終的にローカルで実装する以上、なぜ中間に委任実装を追加するのか.では、下を見続けます.
これは,以上の2つの他に動的実装があるためであり,委任実装とは,ローカル実装と動的実装との間を切り替えるためにすぎない.動的実装はバイトコード技術である.
しかし、一度だけ呼び出されると、動的実装は、動的実装の動作バイトコードが遅いため、ローカル実装は動的実装ブロックよりも少し遅い.JVMは、メソッド呼び出しが毎回少ない(または1回しか呼び出さない)と判断するので、16を設定し、15以上呼び出された場合、つまり17回目から動的実装を使用します.Java->c+->Javaのプロセスを経なければならないため、ローカル実装を使用します.17回目から、JVMはすでに生成されたバイトコードを利用してメソッド呼び出しを行うので、これは間違いなく増加します!
上のコードを20回繰り返して、直感的に見てみましょう.
/**
* @author gzd
* @date 2018-09-09 5:59
* @desc
*/
public class HowReflect {
public static void targetMethod(int i) {
//
new Exception(" " + i)
.printStackTrace();
}
public static void main(String[] args) throws Exception {
Class> howReflect = Class.forName("com.ynwa.jvm.HowReflect");
Method method = howReflect.getMethod("targetMethod", int.class);
for (int i = 0; i < 20; i++) {
//
method.invoke(null, i);
}
}
}
次に、実行結果を示します.
java.lang.Exception: 0
at com.ynwa.jvm.HowReflect.targetMethod(HowReflect.java:14)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.ynwa.jvm.HowReflect.main(HowReflect.java:23)
java.lang.Exception: 1
at com.ynwa.jvm.HowReflect.targetMethod(HowReflect.java:14)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.ynwa.jvm.HowReflect.main(HowReflect.java:23)
// 2 -14 ... ... ...
java.lang.Exception: 15
at com.ynwa.jvm.HowReflect.targetMethod(HowReflect.java:14)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
// 0 -15
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.ynwa.jvm.HowReflect.main(HowReflect.java:23)
java.lang.Exception: 16
at com.ynwa.jvm.HowReflect.targetMethod(HowReflect.java:14)
// !
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.ynwa.jvm.HowReflect.main(HowReflect.java:23)
java.lang.Exception: 17
at com.ynwa.jvm.HowReflect.targetMethod(HowReflect.java:14)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.ynwa.jvm.HowReflect.main(HowReflect.java:23)
java.lang.Exception: 18
at com.ynwa.jvm.HowReflect.targetMethod(HowReflect.java:14)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.ynwa.jvm.HowReflect.main(HowReflect.java:23)
java.lang.Exception: 19
at com.ynwa.jvm.HowReflect.targetMethod(HowReflect.java:14)
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.ynwa.jvm.HowReflect.main(HowReflect.java:23)
結果から、「バージョン17」からダイナミックインプリメンテーションが使用されていることがわかります.
呼び出しのオーバーヘッドを反射
さっきのコードの中でClassforNameはローカルメソッドを呼び出し、getMethodはすべての共有メソッドを巡回し、見つからない場合は親に検索します.これらの操作は時間がかかります.上のコードを改造して1億回実行すれば、私のパソコンの結果は550 msです.直接呼び出すには5 msしかかかりません.格差は非常に大きいことがわかるが,他のプロセスの影響,メソッドのインライン,Intergerのデータキャッシュなど,実験は比較的粗末である.次の改造後のローカルコード:
public static void targetMethod(int i) {
//
// new Exception(" " + i)
// .printStackTrace();
}
public static void main(String[] args) throws Exception {
Class> howReflect = Class.forName("com.ynwa.jvm.HowReflect");
Method method = howReflect.getMethod("targetMethod", int.class);
long start = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
//
// targetMethod(128);
//
method.invoke(i);
}
long end = System.currentTimeMillis();
System.out.println(" :" + (end - start));
}