java反射及Method的Invoke()方法

  用傳統的OOP思想來說,任何一個你寫好的且編譯過的生成的Class文件,在被類加載器加載後,都會對應有一個java.lang.Class這個類的實例。所以說,每個類的自有的方法屬性(類結構)自然被包含在了這個對應的實例上,因此就可以獲取到。

一、原理簡介

public class TestClassLoad {  
    public static void main(String[] args) throws Exception {  
        Class<?> clz = Class.forName("A");  
        Object o = clz.newInstance();  
        Method m = clz.getDeclaredMethod("hello", null);  
        m.invoke(o);   
    }
    static class A{
        public void hello() {
            System.out.println("hello world");
        }
    }  
}  

  上面就是最常見的反射使用的例子,前兩行實現了類的裝載、鏈接和初始化(newInstance方法實際上也是使用反射調用了方法),後兩行實現了從class對象中獲取到method對象然後執行反射調用。下面簡單分析一下後兩行的原理。

  設想一下,如果想要實現method.invoke(action,null)調用action對象的myMethod方法,只需要實現這樣一個Method類即可:

Class Method{
     public Object invoke(Object obj,Object[] param){
        A instance=(A)obj;
        return instance.foo();
     }
}

反射的原理之一其實就是動態的生成類似於上述的字節碼,加載到jvm中運行。

二、獲取Method對象

  調用Class類的getDeclaredMethod可以獲取指定方法名和參數的方法對象Method。
  getDeclaredMethod()方法

public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader(), true);
        Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes); //關注這裏的兩個方法
        if (method == null) {
            throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
        }
        return method;
    }

  其中privateGetDeclaredMethods方法從緩存或JVM中獲取該Class中申明的方法列表,searchMethods方法將從返回的方法列表裏找到一個匹配名稱和參數的方法對象。

private static Method searchMethods(Method[] methods,String name,
                                    Class<?>[] parameterTypes){
        Method res = null;
        String internedName = name.intern();
        for (int i = 0; i < methods.length; i++) {
            Method m = methods[i];
            if (m.getName() == internedName
                && arrayContentsEq(parameterTypes, m.getParameterTypes())
                && (res == null
                    || res.getReturnType().isAssignableFrom(m.getReturnType())))
                res = m;
        }

        return (res == null ? res : getReflectionFactory().copyMethod(res));
    }

  如果找到一個匹配的Method,則重新copy一份返回,即Method.copy()方法

Method copy() { 
        Method res = new Method(clazz, name, parameterTypes, returnType,
                                exceptionTypes, modifiers, slot, signature,
                                annotations, parameterAnnotations, annotationDefault);
        res.root = this;
        res.methodAccessor = methodAccessor;
        return res;
    }

  所次每次調用getDeclaredMethod方法返回的Method對象其實都是一個新的對象,且新對象的root屬性都指向原來的Method對象,如果需要頻繁調用,最好把Method對象緩存起來。
  接下來看privateGetDeclaredMethods()方法,用於從緩存或JVM中獲取該Class中申明的方法列表,代碼如下:

private Method[] privateGetDeclaredMethods(boolean publicOnly) {
        checkInitted();
        Method[] res;
        ReflectionData<T> rd = reflectionData();
        if (rd != null) {
            res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
            if (res != null) return res;
        }
        // No cached value available; request value from VM
        res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
        if (rd != null) {
            if (publicOnly) {
                rd.declaredPublicMethods = res;
            } else {
                rd.declaredMethods = res;
            }
        }
        return res;
    }

  其中reflectionData()方法實現如下:

// Lazily create and cache ReflectionData
    private ReflectionData<T> reflectionData() {
        SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
        int classRedefinedCount = this.classRedefinedCount;
        ReflectionData<T> rd;
        if (useCaches &&
            reflectionData != null &&
            (rd = reflectionData.get()) != null &&
            rd.redefinedCount == classRedefinedCount) {
            return rd;
        }
        // else no SoftReference or cleared SoftReference or stale ReflectionData
        // -> create and replace new instance
        return newReflectionData(reflectionData, classRedefinedCount);
    }

  這裏有個比較重要的數據結構ReflectionData,用來緩存從JVM中讀取類的如下屬性數據:

// reflection data that might get invalidated when JVM TI RedefineClasses() is called
    private static class ReflectionData<T> {
        volatile Field[] declaredFields;
        volatile Field[] publicFields;
        volatile Method[] declaredMethods;
        volatile Method[] publicMethods;
        volatile Constructor<T>[] declaredConstructors;
        volatile Constructor<T>[] publicConstructors;
        // Intermediate results for getFields and getMethods
        volatile Field[] declaredPublicFields;
        volatile Method[] declaredPublicMethods;
        volatile Class<?>[] interfaces;

        // Value of classRedefinedCount when we created this ReflectionData instance
        final int redefinedCount;

        ReflectionData(int redefinedCount) {
            this.redefinedCount = redefinedCount;
        }
    }

  從reflectionData()方法實現可以看出:reflectionData對象是SoftReference類型的,說明在內存緊張時可能會被回收,不過也可以通過-XX:SoftRefLRUPolicyMSPerMB參數控制回收的時機,只要發生GC就會將其回收,如果reflectionData被回收之後,又執行了反射方法,那隻能通過newReflectionData方法重新創建一個這樣的對象了,newReflectionData方法實現如下:

private ReflectionData<T> newReflectionData(SoftReference<ReflectionData<T>> oldReflectionData,
                                                int classRedefinedCount) {
        if (!useCaches) return null;

        while (true) {
            ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount);
            // try to CAS it...
            if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) {
                return rd;
            }
            // else retry
            oldReflectionData = this.reflectionData;
            classRedefinedCount = this.classRedefinedCount;
            if (oldReflectionData != null &&
                (rd = oldReflectionData.get()) != null &&
                rd.redefinedCount == classRedefinedCount) {
                return rd;
            }
        }
    }
static <T> boolean casReflectionData(Class<?> clazz,
                                             SoftReference<ReflectionData<T>> oldData,
                                             SoftReference<ReflectionData<T>> newData) {
            return unsafe.compareAndSwapObject(clazz, reflectionDataOffset, oldData, newData);
        }

  方法調用了casReflectionData(),通過unsafe.compareAndSwapObject方法重新設置reflectionData字段;
在privateGetDeclaredMethods方法中,如果通過reflectionData()獲得的ReflectionData對象不爲空,則嘗試從ReflectionData對象中獲取declaredMethods屬性,如果是第一次,或則被GC回收之後,重新初始化後的類屬性爲空,則需要重新到JVM中獲取一次,並賦值給ReflectionData,下次調用就可以使用緩存數據了。

三、invoke()方法

1、MethodAccessor

public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass(1);

                checkAccess(caller, clazz, obj, modifiers);
            }
        }
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor(); //獲取methodAccessor方法
        }
        return ma.invoke(obj, args);
    }

private MethodAccessor 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;
        } else {
            // Otherwise fabricate one and propagate it up to the root
            tmp = reflectionFactory.newMethodAccessor(this);
            setMethodAccessor(tmp);
        }

        return tmp;
    }

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

public interface MethodAccessor {   
    public Object invoke(Object obj, Object[] args)  
        throws IllegalArgumentException, InvocationTargetException;  
}  

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

  創建MethodAccessor實例的是ReflectionFactory,裏面方法如下。

public MethodAccessor newMethodAccessor(Method var1) {
        checkInitted();
        if(noInflation) { //默認爲false
            return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), 
            var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
        } else {
            NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
            DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
            var2.setParent(var3);
            return var3;
        }
    }

  可以看到生成MethodAccessor有兩個版本,一個是Java實現的,另一個是native code實現的。Java實現的版本在初始化時需要較多時間,但長久來說性能較好;native版本正好相反,啓動時相對較快,但運行時間長了之後速度就比不過Java版了。這是HotSpot的優化方式帶來的性能特性,同時也是許多虛擬機的共同點:跨越native邊界會對優化有阻礙作用,它就像個黑箱一樣讓虛擬機難以分析也將其內聯,於是運行時間長了之後反而是託管版本的代碼更快些。
  爲了權衡兩個版本的性能,Sun的JDK使用了“inflation”的技巧:讓Java方法在被反射調用時,開頭15次使用native版,等反射調用次數超過閾值時則生成一個專用的MethodAccessor實現類,生成其中的invoke()方法的字節碼,以後對該Java方法的反射調用就會使用Java版。

2、native

在ReflectionFactory類中,noInflation默認爲false,方法newMethodAccessor都會返回DelegatingMethodAccessorImpl對象

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;

    DelegatingMethodAccessorImpl(MethodAccessorImpl var1) {
        this.setDelegate(var1);
    }

    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        return this.delegate.invoke(var1, var2);
    }

    void setDelegate(MethodAccessorImpl var1) {
        this.delegate = var1;
    }
}

  其實,DelegatingMethodAccessorImpl對象就是一個代理對象,負責調用被代理對象delegate的invoke方法,其中delegate參數目前是NativeMethodAccessorImpl對象,所以最終Method的invoke方法調用的是NativeMethodAccessorImpl對象invoke方法,實現如下:
  NativeMethodAccessorImpl類

class NativeMethodAccessorImpl extends MethodAccessorImpl {  
    private Method method;  
    private DelegatingMethodAccessorImpl parent;//這是一個間接層,方便在native與Java版的MethodAccessor之間實現切換
    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);  
}  

  每次調用時,次數計數器加一,一旦超過閾值,則通過generateMethod方法生成Java版的MethodAccessor的實現類,並設置爲delegate對象,這樣下次執行Method.invoke時,就調用新建的MethodAccessor對象的invoke()方法了。
  DelegatingMethodAccessorImpl就是一個間接層,方便在native與Java版的MethodAccessor之間實現切換。
  注意到關鍵的invoke0()方法是個native方法。它在HotSpot VM裏是由JVM_InvokeMethod()函數所支持的。

3、java版

  回到Java的一側。generateMethod方法在生成MethodAccessorImpl對象時,會在內存中生成對應的字節碼,並調用ClassDefiner.defineClass創建對應的class對象,部分代碼如下:

return (MagicAccessorImpl)AccessController.doPrivileged(new PrivilegedAction() {
                public MagicAccessorImpl run() {
                    try {
                        return (MagicAccessorImpl)ClassDefiner.defineClass(var13, var17, 0, var17.length, var1.getClassLoader()).newInstance();
                    } catch (IllegalAccessException | InstantiationException var2) {
                        throw new InternalError(var2);
                    }
                }
            });

  在ClassDefiner.defineClass方法實現中,每被調用一次都會生成一個DelegatingClassLoader類加載器對象

static Class<?> defineClass(String var0, byte[] var1, int var2, int var3, final ClassLoader var4) {
        ClassLoader var5 = (ClassLoader)AccessController.doPrivileged(new PrivilegedAction() {
            public ClassLoader run() {
                return new DelegatingClassLoader(var4);
            }
        });
        return unsafe.defineClass(var0, var1, var2, var3, var5, (ProtectionDomain)null);
    }

  這裏每次都生成新的類加載器,是爲了性能考慮,在某些情況下可以卸載這些生成的類,因爲類的卸載是只有在類加載器可以被回收的情況下才會被回收的,如果用了原來的類加載器,那可能導致這些新創建的類一直無法被卸載,從其設計來看本身就不希望這些類一直存在內存裏的,在需要的時候有就行了。
  對本文開頭的例子的A.hello(),生成的Java版MethodAccessor大致如下:

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.hello(arg0);  
        } catch (Throwable t) {  
            throw new InvocationTargetException(t);  
        }  
    }  
}  

  就反射調用而言,這個invoke()方法非常乾淨(然而就“正常調用”而言這額外開銷還是明顯的)。注意到參數數組被拆開了,把每個參數都恢復到原本沒有被Object[]包裝前的樣子,然後對目標方法做正常的invokevirtual調用。由於在生成代碼時已經循環遍歷過參數類型的數組,生成出來的代碼裏就不再包含循環了。

4、性能比較

性能比較
  從變化趨勢上看,第1次和第16次調用是最耗時的(初始化NativeMethodAccessorImpl和字節碼拼裝MethodAccessorImpl)。畢竟初始化是不可避免的,而native方式的初始化會更快,因此前幾次的調用會採用native方法。
  隨着調用次數的增加,每次反射都使用JNI跨越native邊界會對優化有阻礙作用,相對來說使用拼裝出的字節碼可以直接以Java調用的形式實現反射,發揮了JIT優化的作用,避免了JNI爲了維護OopMap(HotSpot用來實現準確式GC的數據結構)進行封裝/解封裝的性能損耗。因此在已經創建了MethodAccessor的情況下,使用Java版本的實現會比native版本更快。所以當調用次數到達一定次數(15次)後,會切換成Java實現的版本,來優化未來可能的更頻繁的反射調用。

轉載自:http://rednaxelafx.iteye.com/blog/548536
http://www.fanyilun.me/2015/10/29/Java%E5%8F%8D%E5%B0%84%E5%8E%9F%E7%90%86/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章