1. 程式人生 > >Method的Invoke方法

Method的Invoke方法

Method的invoke方法 1.先檢查 AccessibleObject的override屬性是否為true。 AccessibleObject是Method,Field,Constructor的父類,override屬性預設為false,可呼叫setAccessible方法改變,如果設定為true,則表示可以忽略訪問許可權的限制,直接呼叫。 2.如果不是ture,則要進行訪問許可權檢測。用Reflection的quickCheckMemberAccess方法先檢查是不是public的,如果不是再用Reflection.getCallerClass(1)方法獲 得到呼叫這個方法的Class,然後做是否有許可權訪問的校驗,校驗之後快取一次,以便下次如果還是這個類來呼叫就不用去做校驗了,直接用上次的結果,(很奇怪用這種方式快取,因為這種方式如果下次換個類來呼叫的話,就不用會快取了,而再驗證一遍,把這次的結果做為快取,但上一次的快取結果就被沖掉了。這是一個很簡單的緩衝機制,只適用於一個類的重複呼叫)。 3.呼叫MethodAccessor的invoke方法。每個Method物件包含一個root物件,root物件裡持有一個MethodAccessor物件。我們獲得的Method獨享相當於一個root物件的映象,所有這類Method共享root裡的MethodAccessor物件,(這個物件由ReflectionFactory方法生成,ReflectionFactory物件在Method類中是static final的由native方法例項化)。 ReflectionFactory生成MethodAccessor:如果noInflation的屬性為true則直接返回MethodAccessorGenerator建立的一個MethodAccessor。 否則返回DelegatingMethodAccessorImpl,並將他與一個NativeMethodAccessorImpl互相引用。但DelegatingMethodAccessorImpl執行invoke方法的時候又委託給NativeMethodAccessorImpl了。 再一步深入 4.NativeMethodAccessorImpl的invkoe方法: 呼叫natiave方法invoke0執行方法呼叫. 注意這裡有一個計數器numInvocations,每呼叫一次方法+1,當比 ReflectionFactory.inflationThreshold(15)大的時候,用MethodAccessorGenerator建立一個MethodAccessor,並把之前的DelegatingMethodAccessorImpl引用替換為現在新建立的。下一次DelegatingMethodAccessorImpl就不會再交給NativeMethodAccessorImpl執行了,而是交給新生成的java位元組碼的MethodAccessor。 MethodAccessorGenerator使用了asm位元組碼動態載入技術,暫不深入研究。 總結 一個方法可以生成多個Method物件,但只有一個root物件,主要用於持有一個MethodAccessor物件,這個物件也可以認為一個方法只有一個,相當於是static的。因為Method的invoke是交給MethodAccessor執行的,所以我所想要知道的答案在MethodAccessor的invoke中,深入MethodAccessor: ------------------------------------------MethodAccessor--------------------------------- 假如有這麼一個類A: 

public class A {
  public void foo(String name) {
    System.out.println("Hello, " + name);
  }
}

可以編寫另外一個類來反射呼叫A上的方法: 

import java.lang.reflect.Method;

public class TestClassLoad {
  public static void main(String[] args) throws Exception {
    Class<?> clz = Class.forName("A");
    Object o = clz.newInstance();
    Method m = clz.getMethod("foo", String.class);
    for (int i = 0; i < 16; i++) {
      m.invoke(o, Integer.toString(i));
    }
  }
}


注意到TestClassLoad類上不會有對類A的符號依賴——也就是說在載入並初始化TestClassLoad類時不需要關心類A的存在與否,而是等到main()方法執行到呼叫Class.forName()時才試圖對類A做動態載入;這裡用的是一個引數版的forName(),也就是使用當前方法所在類的ClassLoader來載入,並且初始化新載入的類。……好吧這個細節跟主題沒啥關係。 

回到主題。這次我的測試環境是Sun的JDK 1.6.0 update 13 build 03。編譯上述程式碼,並在執行TestClassLoad時加入-XX:+TraceClassLoading引數(或者-verbose:class或者直接-verbose都行),如下:
控制檯命令 java -XX:+TraTestClassLoad  ceClassLoading  可以看到輸出了一大堆log,把其中相關的部分截取出來如下: 複製程式碼
[Loaded TestClassLoad from file:/D:/temp_code/test_java_classload/]
[Loaded A from file:/D:/temp_code/test_java_classload/]
[Loaded sun.reflect.NativeMethodAccessorImpl from shared objects file]
[Loaded sun.reflect.DelegatingMethodAccessorImpl from shared objects file]
Hello, 
0 Hello, 1 Hello, 2 Hello, 3 Hello, 4 Hello, 5 Hello, 6 Hello, 7 Hello, 8 Hello, 9 Hello, 10 Hello, 11 Hello, 12 Hello, 13 Hello, 14 [Loaded sun.reflect.ClassFileConstants from shared objects file] [Loaded sun.reflect.AccessorGenerator from shared objects file] [Loaded sun.reflect.MethodAccessorGenerator from shared objects file] [Loaded sun.reflect.ByteVectorFactory from shared objects file] [Loaded sun.reflect.ByteVector from shared objects file] [Loaded sun.reflect.ByteVectorImpl from shared objects file] [Loaded sun.reflect.ClassFileAssembler from shared objects file] [Loaded sun.reflect.UTF8 from shared objects file] [Loaded java.lang.Void from shared objects file] [Loaded sun.reflect.Label from shared objects file] [Loaded sun.reflect.Label$PatchInfo from shared objects file] [Loaded java.util.AbstractList$Itr from shared objects file] [Loaded sun.reflect.MethodAccessorGenerator$1 from shared objects file] [Loaded sun.reflect.ClassDefiner from shared objects file] [Loaded sun.reflect.ClassDefiner$1 from shared objects file] [Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__] Hello, 15
複製程式碼

可以看到前15次反射呼叫A.foo()方法並沒有什麼稀奇的地方,但在第16次反射呼叫時似乎有什麼東西被觸發了,導致JVM新載入了一堆類,其中就包括[Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]這麼一行。這是哪裡來的呢? 

先來看看JDK裡Method.invoke()是怎麼實現的。 
java.lang.reflect.Method: 

複製程式碼
public final
    class Method extends AccessibleObject implements GenericDeclaration, 
                             Member {
    // ...
    
    private volatile MethodAccessor methodAccessor;
    // For sharing of MethodAccessors. This branching structure is
    // currently only two levels deep (i.e., one root Method and
    // potentially many Method objects pointing to it.)
    private Method              root;

    // ...
    
    public Object invoke(Object obj, Object... args)
            throws IllegalAccessException, IllegalArgumentException,
            InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class caller = Reflection.getCallerClass(1);
                Class targetClass = ((obj == null || !Modifier.isProtected(modifiers))
                                     ? clazz
                                     : obj.getClass());
                boolean cached;
                synchronized (this) {
                    cached = (securityCheckCache == caller)
                        && (securityCheckTargetClassCache == targetClass);
                }
                if (!cached) {
                    Reflection.ensureMemberAccess(caller, clazz, obj, modifiers);
                    synchronized (this) {
                    securityCheckCache = caller;
                    securityCheckTargetClassCache = targetClass;
                    }
                }
            }
        }
        if (methodAccessor == null) acquireMethodAccessor();
        return methodAccessor.invoke(obj, args);
    }
    
    // NOTE that there is no synchronization used here. It is correct
    // (though not efficient) to generate more than one MethodAccessor
    // for a given Method. However, avoiding synchronization will
    // probably make the implementation more scalable.
    private void acquireMethodAccessor() {
        // First check to see if one has been created yet, and take it
        // if so
        MethodAccessor tmp = null;
        if (root != null) tmp = root.getMethodAccessor();
        if (tmp != null) {
            methodAccessor = tmp;
            return;
        }
        // Otherwise fabricate one and propagate it up to the root
        tmp = reflectionFactory.newMethodAccessor(this);
        setMethodAccessor(tmp);
    }
    
    // ...
}
複製程式碼

  可以看到Method.invoke()實際上並不是自己實現的反射呼叫邏輯,而是委託給sun.reflect.MethodAccessor來處理。 
每個實際的Java方法只有一個對應的Method物件作為root,。這個root是不會暴露給使用者的,而是每次在通過反射獲取Method物件時新建立Method物件把root包裝起來再給使用者。在第一次呼叫一個實際Java方法對應得Method物件的invoke()方法之前,實現呼叫邏輯的MethodAccessor物件還沒建立;等第一次呼叫時才新建立MethodAccessor並更新給root,然後呼叫MethodAccessor.invoke()真正完成反射呼叫。 

那麼MethodAccessor是啥呢? 

sun.reflect.MethodAccessor:

public interface MethodAccessor {
    /** Matches specification in {@link java.lang.reflect.Method} */
    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException;
}

  可以看到它只是一個單方法介面,其invoke()方法與Method.invoke()的對應。 

建立MethodAccessor例項的是ReflectionFactory。

sun.reflect.ReflectionFactory:

複製程式碼
public class ReflectionFactory {
    
    private static boolean initted = false;
    
    // ...

    //
    // "Inflation" mechanism. Loading bytecodes to implement
    // Method.invoke() and Constructor.newInstance() currently costs
    // 3-4x more than an invocation via native code for the first
    // invocation (though subsequent invocations have been benchmarked
    // to be over 20x faster). Unfortunately this cost increases
    // startup time for certain applications that use reflection
    // intensively (but only once per class) to bootstrap themselves.
    // To avoid this penalty we reuse the existing JVM entry points
    // for the first few invocations of Methods and Constructors and
    // then switch to the bytecode-based implementations.
    //
    // Package-private to be accessible to NativeMethodAccessorImpl
    // and NativeConstructorAccessorImpl
    private static boolean noInflation        = false;
    private static int     inflationThreshold = 15;
    
    // ...
    
    /** We have to defer full initialization of this class until after
        the static initializer is run since java.lang.reflect.Method's
        static initializer (more properly, that for
        java.lang.reflect.AccessibleObject) causes this class's to be
        run, before the system properties are set up. */
    private static void checkInitted() {
        if (initted) return;
        AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    // Tests to ensure the system properties table is fully
                    // initialized. This is needed because reflection code is
                    // called very early in the initialization process (before
                    // command-line arguments have been parsed and therefore
                    // these user-settable properties installed.) We assume that
                    // if System.out is non-null then the System class has been
                    // fully initialized and that the bulk of the startup code
                    // has been run.

                    if (System.out == null) {
                        // java.lang.System not yet fully initialized
                        return null;
                    }

                    String val = System.getProperty("sun.reflect.noInflation");
                    if (val != null && val.equals("true")) {
                        noInflation = true;
                    }

                    val = System.getProperty("sun.reflect.inflationThreshold");
                    if (val != null) {
                        try {
                            inflationThreshold = Integer.parseInt(val);
                        } catch (NumberFormatException e) {
                            throw (RuntimeException) 
                                new RuntimeException("Unable to parse property sun.reflect.inflationThreshold").
                                    initCause(e);
                        }
                    }

                    initted = true;
                    return null;
                }
            });
    }
    
    // ...
    
    public MethodAccessor newMethodAccessor(Method method) {
        checkInitted();

        if (noInflation) {
            return new MethodAccessorGenerator().
                generateMethod(method.getDeclaringClass(),
                               method.getName(),
                               method.getParameterTypes(),
                               method.getReturnType(),
                               method.getExceptionTypes(),
                               method.getModifiers());
        } else {
            NativeMethodAccessorImpl acc =
                new NativeMethodAccessorImpl(method);
            DelegatingMethodAccessorImpl res =
                new DelegatingMethodAccessorImpl(acc);
            acc.setParent(res);
            return res;
        }
    }
}
複製程式碼

  這裡就可以看到有趣的地方了。如註釋所述,實際的MethodAccessor實現有兩個版本,一個是Java實現的,另一個是native code實現的。Java實現的版本在初始化時需要較多時間,但長久來說效能較好;native版本正好相反,啟動時相對較快,但執行時間長了之後速度就比不過Java版了。這是HotSpot的優化方式帶來的效能特性,同時也是許多虛擬機器的共同點:跨越native邊界會對優化有阻礙作用,它就像個黑箱一樣讓虛擬機器難以分析也將其內聯,於是執行時間長了之後反而是託管版本的程式碼更快些。 
  為了權衡兩個版本的效能,Sun的JDK使用了“inflation”的技巧:讓Java方法在被反射呼叫時,開頭若干次使用native版,等反射呼叫次數超過閾值時則生成一個專用的MethodAccessor實現類,生成其中的invoke()方法的位元組碼,以後對該Java方法的反射呼叫就會使用Java版。 
Sun的JDK是從1.4系開始採用這種優化的。

  PS.可以在啟動命令里加上-Dsun.reflect.noInflation=true,就會RefactionFactory的noInflation屬性就變成true了,這樣不用等到15呼叫後,程式一開始就會用java版的MethodAccessor了。

上面看到了ReflectionFactory.newMethodAccessor()生產MethodAccessor的邏輯,在“開頭若干次”時用到的DelegatingMethodAccessorImpl程式碼如下: 

sun.reflect.DelegatingMethodAccessorImpl:

複製程式碼
/** Delegates its invocation to another MethodAccessorImpl and can
    change its delegate at run time. */

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;

    DelegatingMethodAccessorImpl(MethodAccessorImpl delegate) {
        setDelegate(delegate);
    }    

    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException
    {
        return delegate.invoke(obj, args);
    }

    void setDelegate(MethodAccessorImpl delegate) {
        this.delegate = delegate;
    }
}
複製程式碼

這是一個間接層,方便在native與Java版的MethodAccessor之間實現切換。 

然後下面就是native版MethodAccessor的Java一側的宣告:

sun.reflect.NativeMethodAccessorImpl:

複製程式碼
/** Used only for the first few invocations of a Method; afterward,
    switches to bytecode-based implementation */

class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;

    NativeMethodAccessorImpl(Method method) {
        this.method = method;
    }    

    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException
    {
        if (++numInvocations > ReflectionFactory.inflationThreshold()) {
            MethodAccessorImpl acc = (MethodAccessorImpl)
                new MethodAccessorGenerator().
                    generateMethod(method.getDeclaringClass(),
                                   method.getName(),
                                   method.getParameterTypes(),
                                   method.getReturnType(),
                                   method.getExceptionTypes(),
                                   method.getModifiers());
            parent.setDelegate(acc);
        }
        
        return invoke0(method, obj, args);
    }

    void setParent(DelegatingMethodAccessorImpl parent) {
        this.parent = parent;
    }

    private static native Object invoke0(Method m, Object obj, Object[] args);
}
複製程式碼

  每次NativeMethodAccessorImpl.invoke()方法被呼叫時,都會增加一個呼叫次數計數器,看超過閾值沒有;一旦超過,則呼叫MethodAccessorGenerator.generateMethod()來生成Java版的MethodAccessor的實現類,並且改變DelegatingMethodAccessorImpl所引用的MethodAccessor為Java版。後續經由DelegatingMethodAccessorImpl.invoke()呼叫到的就是Java版的實現了。

注意到關鍵的invoke0()方法是個native方法。它在HotSpot VM裡是由JVM_InvokeMethod()函式所支援的:

由C編寫

JNIEXPORT jobject JNICALL Java_sun_reflect_NativeMethodAccessorImpl_invoke0
(JNIEnv *env, jclass unused, jobject m, jobject obj, jobjectArray args)
{
    return JVM_InvokeMethod(env, m, obj, args);
}
複製程式碼
JVM_ENTRY(jobject, JVM_InvokeMethod(JNIEnv *env, jobject method, jobject obj, jobjectArray args0))
  JVMWrapper("JVM_InvokeMethod");
  Handle method_handle;
  if (thread->stack_available((address) &method_handle) >= JVMInvokeMethodSlack) {
    method_handle = Handle(THREAD, JNIHandles::resolve(method));
    Handle receiver(THREAD, JNIHandles::resolve(obj));
    objArrayHandle args(THREAD, objArrayOop(JNIHandles::resolve(args0)));
    oop result = Reflection::invoke_method(method_handle(), receiver, args, CHECK_NULL);
    jobject res = JNIHandles::make_local(env, result);
    if (JvmtiExport::should_post_vm_object_alloc()) {
      oop ret_type = java_lang_reflect_Method::return_type(method_handle());
      assert(ret_type != NULL, "sanity check: ret_type oop must not be NULL!");
      if (java_lang_Class::is_primitive(ret_type)) {
        // Only for primitive type vm allocates memory for java object.
        // See box() method.
        JvmtiExport::post_vm_object_alloc(JavaThread::current(), result);
      }
    }
    return res;
  } else {
    THROW_0(vmSymbols::java_lang_StackOverflowError());
  }
JVM_END
複製程式碼

其中的關鍵又是Reflection::invoke_method(): 

複製程式碼
// This would be nicer if, say, java.lang.reflect.Method was a subclass
// of java.lang.reflect.Constructor

oop Reflection::invoke_method(oop method_mirror, Handle receiver, objArrayHandle args, TRAPS) {
  oop mirror             = java_lang_reflect_Method::clazz(method_mirror);
  int slot               = java_lang_reflect_Method::slot(method_mirror);
  bool override          = java_lang_reflect_Method::override(method_mirror) != 0;
  objArrayHandle ptypes(THREAD, objArrayOop(java_lang_reflect_Method::parameter_types(method_mirror)));

  oop return_type_mirror = java_lang_reflect_Method::return_type(method_mirror);
  BasicType rtype;
  if (java_lang_Class::is_primitive(return_type_mirror)) {
    rtype = basic_type_mirror_to_basic_type(return_type_mirror, CHECK_NULL);
  } else {
    rtype = T_OBJECT;
  }

  instanceKlassHandle klass(THREAD, java_lang_Class::as_klassOop(mirror));
  methodOop m = klass->method_with_idnum(slot);
  if (m == NULL) {
    THROW_MSG_0(vmSymbols::java_lang_InternalError(), "invoke");
  }
  methodHandle method(THREAD, m);

  return invoke(klass, method, receiver, override, ptypes, rtype, args, true, THREAD);
}
複製程式碼

  再下去就深入到HotSpot VM的內部了,本文就在這裡打住吧。有同學有興趣深究的話以後可以再寫一篇討論native版的實現。

  回到Java的一側。MethodAccessorGenerator長啥樣呢?由於程式碼太長,這裡就不完整貼了,有興趣的可以到OpenJDK 6的Mercurial倉庫看:OpenJDK 6 build 17的MethodAccessorGenerator。它的基本工作就是在記憶體裡生成新的專用Java類,並將其載入。就貼這麼一個方法:

複製程式碼
private static synchronized String generateName(boolean isConstructor,
                                                boolean forSerialization)
{
    if (isConstructor) {
        if (forSerialization) {
            int num = ++serializationConstructorSymnum;
            return "sun/reflect/GeneratedSerializationConstructorAccessor" + num;
        } else {
            int num = ++constructorSymnum;
            return "sun/reflect/GeneratedConstructorAccessor" + num;
        }
    } else {
        int num = ++methodSymnum;
        return "sun/reflect/GeneratedMethodAccessor" + num;
    }
}
複製程式碼

  去閱讀原始碼的話,可以看到MethodAccessorGenerator是如何一點點把Java版的MethodAccessor實現類生產出來的。也可以看到GeneratedMethodAccessor+數字這種名字是從哪裡來的了,就在上面的generateName()方法裡。 
對本文開頭的例子的A.foo(),生成的Java版MethodAccessor大致如下:

複製程式碼
package sun.reflect;

public class GeneratedMethodAccessor1 extends MethodAccessorImpl {    
    public GeneratedMethodAccessor1() {
        super();
    }
    
    public Object invoke(Object obj, Object[] args)   
        throws IllegalArgumentException, InvocationTargetException {
        // prepare the target and parameters
        if (obj == null) throw new NullPointerException();
        try {
            A target = (A) obj;
            if (args.length != 1) throw new IllegalArgumentException();
            String arg0 = (String) args[0];
        } catch (ClassCastException e) {
            throw new IllegalArgumentException(e.toString());
        } catch (NullPointerException e) {
            throw new IllegalArgumentException(e.toString());
        }
        // make the invocation
        try {
            target.foo(arg0);
        } catch (Throwable t) {
            throw new InvocationTargetException(t);
        }
    }
}
複製程式碼

  就反射呼叫而言,這個invoke()方法非常乾淨(然而就“正常呼叫”而言這額外開銷還是明顯的)。注意到引數陣列被拆開了,把每個引數都恢復到原本沒有被Object[]包裝前的樣子,然後對目標方法做正常的invokevirtual呼叫。由於在生成程式碼時已經迴圈遍歷過引數型別的陣列,生成出來的程式碼裡就不再包含迴圈了。 

  至此找到我的答案了,因為MethodAccessor會做強制型別轉換再進行方法呼叫,但父類強制轉化成子類的的時候就會報錯型別不匹配錯誤了,所以如果變數的引用宣告是父但實際指向的物件是子,那麼這種呼叫也是可以的。

----------------------------------------------------------題外話------------------------------------------
  當該反射呼叫成為熱點時,它甚至可以被內聯到靠近Method.invoke()的一側,大大降低了反射呼叫的開銷。而native版的反射呼叫則無法被有效內聯,因而呼叫開銷無法隨程式的執行而降低。 
  雖說Sun的JDK這種實現方式使得反射呼叫方法成本比以前降低了很多,但Method.invoke()本身要用陣列包裝引數;而且每次呼叫都必須檢查方法的可見性(在Method.invoke()裡),也必須檢查每個實際引數與形式引數的型別匹配性(在NativeMethodAccessorImpl.invoke0()裡或者生成的Java版MethodAccessor.invoke()裡);而且Method.invoke()就像是個獨木橋一樣,各處的反射呼叫都要擠過去,在呼叫點上收集到的型別資訊就會很亂,影響內聯程式的判斷,使得Method.invoke()自身難以被內聯到呼叫方。 
  相比之下JDK7裡新的MethodHandler則更有潛力,在其功能完全實現後能達到比普通反射呼叫方法更高的效能。在使用MethodHandle來做反射呼叫時,MethodHandle.invoke()的形式引數與返回值型別都是準確的,所以只需要在連結方法的時候才需要檢查型別的匹配性,而不必在每次呼叫時都檢查。而且MethodHandle是不可變值,在建立後其內部狀態就不會再改變了;JVM可以利用這個知識而放心的對它做激進優化,例如將實際的呼叫目標內聯到做反射呼叫的一側。 

  本來Java的安全機制使得不同類之間不是任意資訊都可見,但Sun的JDK裡開了個口,有一個標記類專門用於開後門:

複製程式碼
package sun.reflect;

/** <P> MagicAccessorImpl (named for parity with FieldAccessorImpl and
    others, not because it actually implements an interface) is a
    marker class in the hierarchy. All subclasses of this class are
    "magically" granted access by the VM to otherwise inaccessible
    fields and methods of other classes. It is used to hold the code
    for dynamically-generated FieldAccessorImpl and MethodAccessorImpl
    subclasses. (Use of the word "unsafe" was avoided in this class's
    name to avoid confusion with {@link sun.misc.Unsafe}.) </P>

    <P> The bug fix for 4486457 also necessitated disabling
    verification for this class and all subclasses, as opposed to just
    SerializationConstructorAccessorImpl and subclasses, to avoid
    having to indicate to the VM which of these dynamically-gen