JVMはJava反射をどのように実現していますか?

7456 ワード

Javaの反射アプリケーションシーンは非常に多く、IDEが連想をするとき、SpringのIoCコンテナなどがあります.反射によって、他のオブジェクトのプライベートメソッドを呼び出したり、任意の属性を取得したりするなど、普段できないことをすることができます.つまり、反射の前では、Javaオブジェクトにプライバシーがありません.
反射呼び出しの実装
まず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));
    }